strcpy,memcpy,memmove和内存重叠分析

一:strcpy函数用法和实现:

  /* 

 GNU-C中的实现(节选): 

 */

 char* strcpy(char *d, const char *s) 

 { 

   char *r=d; 

   while((*d++=*s++)); 

   return r; 

 } 


有没有发现这个实现并不是很好。。至少没有判断是不是为NULL。一个更好的版本:

	char *strcpy(char *strDest, const char *strSrc)
	{
		assert((strDest!=NULL) && (strSrc !=NULL));
		char *address = strDest; 
		while( (*strDest++ = * strSrc++) != '\0') 
			 
		return address ; 
	}


好好研究这段代码其实写的挺妙的。。(当然不是我写的)。。

 

二:memcpy函数用法和实现:

从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

微软实现:

 void * __cdecl memcpy (void * dst,const void * src, size_t count ) 
 { 
	 void * ret = dst; 
#if defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC)         
	 {         
		 extern void RtlMoveMemory( void *, const void *, size_t count );
		 RtlMoveMemory( dst, src, count );
	 } 
#else  /* defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) */         
	 /*          * copy from lower addresses to higher addresses          */        
	 while (count--) { 
		 *(char *)dst = *(char *)src;
		 dst = (char *)dst + 1; 
		 src = (char *)src + 1;
	 } 
#endif  /* defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) */        
	 return(ret);
 } 


写的是不是也很好。。仿佛天衣无缝啊!。。但是你有没有注意到一个问题:那就是内存重叠。。

什么叫内存重叠呢?就是两块内存区域有一个共同的部分。。是不是很SB的解释。。当存在这个问题的时候,那还和我们预期的那样拷贝吗?注意:理解了这个才是关键。。

例子:(引用网上一个例子)

int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
 memcpy(a+4, a, sizeof(int)*6) 

如果照微软实现的话,那么输出应该是0123012301(理解这个再忘下看吧。。)

而我们想要达到的目的是输出:0123012345。。如果你实验一下,确实也是输出0123012345。。(对于这个问题,我也解释不了)。为了避免这样的情况发生,我们找到了一个更好的函数:memmove。。

 

三:memmove函数用法和实现

用法和memcpy函数一样。。

实现:

 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;
 }


一开始看到这段代码的时候,觉得好神奇啊。后来发现,其实有个很大的漏洞(先不说那些小问题),那就是内存重叠有两种情况。其实我觉得该作者也想到了,而且给出了解决办法。,估计是粗心大意吧。
:src<dest&&src+count>dest;  dest<src&&dest+count>src。。但是网上那位作者没有写清楚,下面我就重写一下上面的else部分:

 else
	 { 
		 //重叠1:(反向拷贝)
		 if(tmp_source<=tmp_dest&&tmp_source+count>=tmp_dest)
		 {
			 tmp_source += count - 1;
			 tmp_dest += count - 1;
			 while(count--)
				 *tmp_dest-- = *tmp_source--;
		 }
		  //重叠2:(正向拷贝)
		 else
		 {
			 while (count--)
			 {
				 *tmp_dest++=*tmp_source++;
			 }
		 }
	 }


但写完之后发现傻逼了。。后面的那个else不可以和前面的那个if写在一起么?所以优化后的完整代码:

 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(tmp_source<=tmp_dest&&tmp_source+count>=tmp_dest)
	 {
		 tmp_source += count - 1;
		 tmp_dest += count - 1;
		 while(count--)
			 *tmp_dest-- = *tmp_source--;
	 }
	 else
	 {
		 while (count--)
		 {
			 *tmp_dest++=*tmp_source++;
		 }
	 }
	
	 return tmp_dest;
 }

 

此时就加上了内存重叠的判断。如果你理解了上面的那个(输出应该是0123012301),一切都明白了。。还有:别忘了那个问题哟,运行的时候还是输出:0123012345。。和我们的理论值有冲突。。希望大神发现问题能告知,一起学习,交流。。

 

此时才发现,平时写的那些代码,我们能懂他们多少呢?

`memcpy`函数是C/C++中用于内存拷贝的标准库函数。它可以在内存之间复制指定数量的字节。为了避免内存覆盖错误,我们应该遵循以下几个原则: 1. 确保目标内存有足够的空间来接收源内存的内容。这通常意味着目标缓冲区的大小不能小于源内存的大小,否则可能会导致目标缓冲区之外的内存被覆盖,从而引起未定义行为。 2. 使用`memcpy`时,最好指定一个确切的字节数,而不是依赖于`sizeof`运算符,除非你非常确定数据结构的大小。例如,如果你想要复制一个结构体,你应当使用`sizeof(struct_type)`来获取结构体的实际大小。 3. 避免使用`memcpy`来拷贝包含指针的数据,因为这样做拷贝的只是指针本身的值,而不是指针指向的数据。如果需要复制指针指向的数据,应该逐个元素或者使用其他安全的复制函数(比如`strcpy`对于字符串)。 4. 使用`memcpy`时,确保源内存和目标内存重叠,否则结果是未定义的。如果内存区域可能重叠,可以考虑使用`memmove`,因为它能正确处理重叠内存区域的复制。 下面是一个使用`memcpy`的示例代码: ```c #include <stdio.h> #include <string.h> int main() { // 假设有一个结构体类型 struct Data { int a; char b; }; struct Data source = {10, 'A'}; struct Data dest; // 确保dest有足够的空间 // 记得使用结构体的实际大小,而不是使用sizeof(struct Data),以避免潜在的填充字节问题 memcpy(&dest, &source, sizeof(struct Data)); printf("dest.a = %d, dest.b = %c\n", dest.a, dest.b); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值