memcpy使用时需要注意的地方
2018年07月07日 19:16:13 十一月zz 阅读数:981
版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/baidu_35679960/article/details/80953973
测试平台:codeblocks 17.12;
注:文中的图片是读者添加的
1、注意最后一个参数是字节数;
原型:
void * memcpy ( void * destination, const void * source, size_t num );
将source指向的地址处的 num 个字节 拷贝到 destination 指向的地址处。注意加黑的字,是字节。
例如:
int a[10] = {0,1,2,3,4,5,6,7,8,9};
memcpy(a, a + 3, 2);
你猜拷贝过之后a数组的值是什么?
3,4,2,3,4,5,6,7,8,9,
是这样么?
不是!
真实结果是:
3,1,2,3,4,5,6,7,8,9,
为什么?
因为memcpy的最后一个参数是需要拷贝的字节的数目!一个int类型占据4个字节!这样的话,要想达到将a+3地址开始的2个元素拷贝到a地址处,需要这么写:
memcpy(a, a + 3, 2*sizeof(int));
经过这么一条语句,a数组的内容就变成了:
3,4,2,3,4,5,6,7,8,9,
这才是我们想要的!
2、注意内存重叠的问题
在很久很久以前,memcpy的实现大致是这样的:
void* my_memcpy(void* dst, const void* src, size_t n)
{
char *tmp = (char*)dst;
char *s_src = (char*)src;
while(n--) {
*tmp++ = *s_src++;
}
return dst;
}
- 这样的话,当你执行下面的程序时:
#include <iostream>
#include <cstring>
using namespace std;
void* my_memcpy(void* dst, const void* src, size_t n)
{
char *tmp = (char*)dst;
char *s_src = (char*)src;
while(n--) {
*tmp++ = *s_src++;
}
return dst;
}
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
my_memcpy(a + 3, a, 5*sizeof(int));
int i = 0;
for(int i = 0; i < 10; i++){
printf("%d,", a[i]); //0,1,2,0,1,2,0,1,8,9,
}
return 0;
}
猜猜输出是什么?
或许我们想要达到的效果是:0,1,2,0,1,2,3,4,8,9,
这样?
NO!
真实的输出是:
0,1,2,0,1,2,0,1,8,9,
这是因为原地址和目的地址这两段有重叠,导致了如上的输出结果;
这里的重叠是咋回事呢?
看下面的图就明白了:
现在的memcpy都做了改进,能够达到和memmove一样的效果,即使在目的地址和原地址存在重叠时也能达到我们想要的结果。
即:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
memcpy(a + 3, a, 5*sizeof(int)); //memmove(a + 3, a, 5*sizeof(int)); 也行;
int i = 0;
for(int i = 0; i < 10; i++){ //0,1,2,0,1,2,3,4,8,9,
printf("%d,", a[i]);
}
return 0;
}
参考:https://my.oschina.net/zidanzzg/blog/812887
下面另一篇文章帮助上面的理解解决内存的问题:
大家一般认为名不见经传strcpy函数实现不是很难,流行的strcpy函数写法是:
[cpp] view plaincopy
- char *my_strcpy(char *dst,const char *src)
- {
- assert(dst != NULL);
- assert(src != NULL);
- char *ret = dst;
- while((* dst++ = * src++) != '\0')
- ;
- return ret;
- }
如果注意到:
1,检查指针有效性;
2,返回目的指针des;
3,源字符串的末尾 '\0' 需要拷贝。
写出上面实现函数就不在话下。
然而这样的实现没有考虑拷贝时内存重叠的情况,下面的测试用例就能使调用my_strcp函数的程序崩溃:
[cpp] view plaincopy
- char str[10]="abc";
- my_strcpy(str+1,str);
然而调用系统的strcpy函数程序正常运行,打印str结果为“aabc”!可见系统strcpy函数的实现不是这样的。
strcpy的正确实现应为:
[cpp] view plaincopy
- char *my_strcpy(char *dst,const char *src)
- {
- assert(dst != NULL);
- assert(src != NULL);
- char *ret = dst;
- memcpy(dst,src,strlen(src)+1);
- return ret;
- }
memcpy函数实现时考虑到了内存重叠的情况,可以完成指定大小的内存拷贝,它的实现方式建议查看文章“卓越的教练是如何训练高手的?”,会获益良多,这里仅粘帖函数memcpy函数的实现:
[cpp] view plaincopy
- void * my_memcpy(void *dst,const void *src,unsigned int count)
- {
- assert(dst);
- assert(src);
- void * ret = dst;
- if (dst <= src || (char *)dst >= ((char *)src + count))//源地址和目的地址不重叠,低字节向高字节拷贝
- //dst <= src的意思是在同一个字符串中调整部分字符的位置时,目标地址dst的值如果
- //比源字符串的地址小,也就是说要拷贝的字符串的地址在目标字符串首地址的后面,
- //源字符串的值永远不会用的前面字符的值,所以永远不会有地址重叠的现象。
- //(char *)dst >= ((char *)src + count))的意思是如果目标字符串的地址dst大于目标字符
- //串地址src,但是只要满足目标字符串的地址dst在源字符串地址src+要拷贝的字符串的个数后
- //得到的地址即:源字符串的地址不要踏入目标字符串的地址范围,如果踏入了,则源字符串
- //取值时,就会从已经更改过的目标字符串中去取值当做源字符串的值放到目标字符串中。侵
- //占了目标字符串的地址范围,这不是程序员想要的结果。
- {
- while(count--)
- {
- *(char *)dst = *(char *)src;
- dst = (char *)dst + 1;
- src = (char *)src + 1;
- }
- }
- else //源地址和目的地址重叠(重叠的意思是目标地址dst大于原地址src的情况),高字节向低字节拷 //贝(高字节拷贝的意思是把原地址的首地址指针向后移动count-1个字符后的地址,变成 //src+count-1的地址处,然后从这个地址向左逐个复制填充到目标字符串中,这样就不会有
- //要复制的源字符串的值使用的是目标字符串地址范围的值了,也就不会有地址重叠问题了) ,
- {
- dst = (char *)dst + count - 1; //把目标地址往后移动count个(这里是5个),当然包括dst这个地址,所以移动 //count-1个,把要重叠的字符让开。
- src = (char *)src + count - 1; //把源地址往后移动count个(这里是5个),当然包括src这个地址,所以移动 //count-1个,把要重叠的字符让开。然后从原地址的最后一个地址处也就是高地址处
- //开始往左(倒着)复制count次,这样就不会有重叠的问题了
- while(count--)
- {
- *(char *)dst = *(char *)src;
- dst = (char *)dst - 1;
- src = (char *)src - 1;
- }
- }
- return ret;
- }
两者结合才是strcpy函数的真正实现吧。