POSIX和C标准明确表示,使用重叠区域的mem‐cpy()会产生未定义的行为。
memcpy和memmove的区别是:当内存发生局部重叠时memmove函数能够保证拷贝结果的正确性,而memcpy则不能保证拷贝结果的正确性;当内存没有发生重叠的时候两个函数的结果是一样的。
为什么说memcpy不能保证拷贝结果的正确性,可以看看下面这个例子。
当src和dst重合(即内存重叠)且src小于dst且dst < src+count时,如下图所示:
如果从低地址开始拷贝,那么源数据在使用前会被覆盖。除了这种情况,其他情况下都可以保证memcpy拷贝正确。
为什么memmove可以保证拷贝的结果是正确的?
因为memmove是在memcpy的基础上对所有可能的情况都做了处理;简单的说就是在内存重叠且源数据会被覆盖的情况下选择从高地址开始拷贝,而其他情况下选择从低地址开始拷贝。
下面是我自己写的一个memmove实现函数:
void *my_memmove(void *dst, const void *src, int count)
{
if(NULL == dst || NULL == src || count <= 0)
{
return NULL;
}
char *tmp_dst = (char *)dst;
char *tmp_src = (char *)src;
if((tmp_src < tmp_dst) && (tmp_dst <= (tmp_src+count-1)))
{
tmp_src = tmp_src+count-1;
tmp_dst = tmp_dst+count-1;
while(count--) //从高地址开始拷贝
{
*tmp_dst-- = *tmp_src--;
}
}
else
{
while(count--) //从低地址开始拷贝
{
*tmp_dst++ = *tmp_src++;
}
}
return dst;
}
综上,如果你在拷贝内存数据的时候不确定源区域和目标区域内存是否重合,最好还是使用memmove函数进行拷贝,以保证结果的正确性。
最后,再讲一讲memcpy函数的特点。
阅读标准C库memcpy的源码,你会发现memcpy函数内部会根据你要拷贝的字节数来选择不同的处理流程。
void *
memcpy (dstpp, srcpp, len)
void *dstpp;
const void *srcpp;
size_t len;
{
unsigned long int dstp = (long int) dstpp;
unsigned long int srcp = (long int) srcpp;
if (len >= OP_T_THRES)
{
len -= (-dstp) % OPSIZ;
BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
WORD_COPY_FWD (dstp, srcp, len, len);
}
BYTE_COPY_FWD (dstp, srcp, len);
return dstpp;
}
分析:如果拷贝的字节数比较小,就按字节(一个字节一个字节)进行拷贝;如果拷贝的字节数较大,那就先拷贝几个字节使DSTP对齐;然后尽可能通过虚拟地址操作将整个页面(虚拟内存页)从SRCP复制到DSTP。接着利用已知的DSTP对齐,从SRCP复制到DSTP。剩下的字节数放在第三个参数中,即LEN中。这个数字可能因机器而异。最后,还剩余少数字节,我们按字节拷贝即可。
总结1、memcpy拷贝的结果可能不正确;memmove可以保证结果是正确的。在不确定内存是否重合的情况下优先考虑memmove。
2、一般情况下,memcpy由于实现比memmove简单(内部逻辑判断没那么多)效率会高一点。



