1. 程式人生 > >對記憶體重疊的深入認識

對記憶體重疊的深入認識

記憶體重疊:拷貝的目的地址在源地址範圍內。所謂記憶體重疊就是拷貝的目的地址和源地址有重疊。

在函式strcpy和函式memcpy都沒有對記憶體重疊做處理的,使用這兩個函式的時候只有程式設計師自己保證源地址和目標地址不重疊,或者使用memmove函式進行記憶體拷貝。

memmove函式對記憶體重疊做了處理。

現在來看函式strcpy

原型:extern char *strcpy(char *dest,char *source);

功能:把source所指由NULL結束的字串複製到dest所指的陣列中。

說明:source和dest所指記憶體區域不可以重疊且dest必須有足夠的空間來容納source的字串。

返回指向dest的指標。

重疊從兩方面考慮:

(1).dest資料覆蓋了source; 如:dest(8byte) 地址:1000

source(8byte) 地址:1002

(2).dest所指的區域本來就是source的一部分; 如:dest(8byte) 地址:1000

source(8byte) 地址:0998

例如:針對第一種交叉情況情況,dst<src且dst+count>src,memcpy和memmove的結果是一樣的。請看下面的例子講解:

string s = "hello world";

memmove(&s[0],&s[5],10);

舉個記憶體重疊環境的例子:

int main()

{char *p = NULL;

p=(char*)malloc(100);

memcpy(p,"123456789",strlen("123456789")); //會等到錯誤的結果,有一個長度引數,只能拷貝cnt個

//位元組就結束了

printf("before p =%s\n",p);

strcpy(p+1,p); //注意:這裡重疊了,而strcpy是根據判斷原串中的'\0'

printf("after p =%s\n",p);

free(p);

}

1.下面來看strcpy()原型寫法: 字串拷貝.
char *strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL) && (strSrc !=NULL));
char *address = strDest;
while( (*strDest++ = * strSrc++)·1 != '/0')
NULL ;
return address ;
}

2.下面來看下memcpy函式的原型寫法:記憶體拷貝

void *memcpy(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));

char *tmp_dest = (char *)dest;
char *tmp_source = (char *)source;
while(count --)//不對是否存在重疊區域進行判斷
*tmp_dest ++ = *tmp_source ++;
return dest;
}

3.下面來看下memmove函式的原型寫法:

void *memmove(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));
char *tmp_source, *tmp_dest;
tmp_source = (char *)source;
tmp_dest = (char *)dest;
if((dest + count<source) || (source + count) <dest))
{// 如果沒有重疊區域
while(count--)
*tmp_dest++ = *tmp_source++;
}
else
{ //如果有重疊(反向拷貝)
tmp_source += count - 1;
tmp_dest += count - 1;
while(count--)
*--tmp_dest = *--tmp;
}
return dest;
}

深入分析:

void *memcpy(void *dst, const void *src, size_t count):
void *memmove(void *dst, const void *src, size_t count);

先看一個測試:

#include <string.h>

#include <stdio.h>

int main()

{ int a[10];

for(int i=0; i < 10; i++)

a[i] = i;

memcpy (&a[4],a,sizeof(int)*6); //結果為:1 2 3 0 1 2 3 0 1

//memcpy(&a[4], a, sizeof(int)*6); //結果為:1 2 3 0 1 2 3 0 1(vc下和下面一個相同)

//MemMove(&a[4],a,sizeof(int)*6); //結果為:1 2 3 0 1 2 3 4 5

//memmove(&a[4],a,sizeof(int)*6); //結果為:1 2 3 0 1 2 3 4 5

//MemMove(a,&a[4],sizeof(int)*6); //結果為:5 6 7 8 9 6 7 8 9

//memmove(a, &a[4], sizeof(int)*6);//結果為:5 6 7 8 9 6 7 8 9

//memcpy(a, &a[4], sizeof(int)*6); //結果為:5 6 7 8 9 6 7 8 9

//MemCopy(a,&a[4],sizeof(int)*6); //結果為:5 6 7 8 9 6 7 8 9

for(i = 0; i < 10; i++)

printf("%d ",a[i]);

printf("/n");

return 0;

}
它們都是從src所指向的記憶體中複製count個位元組到dst所指記憶體中,並返回dst的值。當源記憶體區域和目標記憶體區域無交叉時,兩者的結果都是一樣的。但有交叉時不一樣。源記憶體和目標記憶體交叉的情況有以下兩種:(左邊為低地址)

即:dst<=src 且 dst+count>src

針對第一種交叉情況情況,dst<=src且dst+count>src,memcpy和memmove的結果是一樣的。請看下面的例子講解:
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
memcpy(a, a+4, sizeof(int)*6);和

memmove(a, a+4, sizeof(int)*6);結果一樣,都是:4567896789

針對第二種情況,src<dst且src+count>dst,memcpy和memmove的結果是不一樣的。請看下面的例子:
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
memcpy(a+4, a, sizeof(int)*6) 結果按照分析應該是:0123012301

但是在vs2005上執行卻是:0123012345(有知道的,請告訴我原因)

memmove(a+4, a, sizeof(int)*6) 結果是:0123012345

總結:

1. 當 src 和 dest 所指記憶體區有重疊時,memmove 相對 memcpy 能提供保證:保證能將 src 所指記憶體區的前 n 個位元組正確的拷貝到 dest 所指記憶體中;
2. 當 src 地址比 dest 地址低時,兩者結果一樣。換句話說,memmove 與 memcpy 的區別僅僅體現在 dest 的頭部和 src 的尾部有重疊的情況下;

綜上所述在進行記憶體重疊的考慮時,strcpy,memcpy都要做一個記憶體重疊的判斷:

對於memcpy需要加上一個斷言:Assert(dst<=src || src+count<dst);

source和dest所指記憶體區域不可以重疊且dest必須有足夠的空間來容納source的字串。

返回指向dest的指標。

對於strcpy需要加上一個斷言:

int count = strlen(src) + 1;//src length

Assert (dest<src || dest>(src+count))

char * strcpy(char *dest, const char *src)

{

    char *d = dest; //backup input

    char *s = src;

    int count = 0;

    assert(dest); //非空指標檢查

    assert(src);

    if(src == dest)

         return src;

    count = strlen(src) + 1;//src length

    if(count<=1)

         return 0; //empty src

    if(dest<src || dest>(src+count))

    {

         while(count--)

             *d++ = *s++;

    }

    else //dest 位於src+count中間,

    {

         d = dest+count;

         s = src+count;

         while(count--)

             *d-- = *s--; //倒過來拷貝

    }