题目来自网上
题目:编写内存拷贝函数void* Memmove(void* dst,const void* src,size_t count)
考点:
(1)内存重叠,即源地址指向的内存和目的地址指向的内存可能有重叠
(2)指针移动时,必须要给出指针类型。
分析:
根据源地址指向的内存和目的地址指向的内存的关系,可以分四种情况
(1)src >= dst + count:两块内存互不重叠
(2)dst >= src + count : 两块内存互不重叠
(3)src < dst && dst < src + count : 两块内存重叠
(4)dst < src && src < dst + count:两块内存重叠
如图所示
(图 1、内存不重叠的情况)
(图 2、内存重叠的情况)
根据上图分析可知:
1、在(1)src >= dst + count 和 (2)dst >= src + count 两种情况下,内存互不重叠的,此时针对目的地址dst,有低地址到高地址拷贝和从高地址到高地址拷贝都是可以正常执行的。程序中采用低地址到高地址拷贝。
2、在(3)src < dst && dst < src + count 和 dst < src && src < dst + count 两种情况下,内存是重叠的。
对于第(3)中情况,只能是先取出源地址src指向内存的高地址位置去填充dst的高地址,之后往低地址开始处理。这是因为src的高地址和dst的低地址是重叠的。对源地址src,如果直接从src的低地址开始拷贝,在拷贝完成前,会把src指向的内存数据污染掉。
对于第(4)中情况,只能是先取出源地址src指向内存的低地址位置去填充dst的低地址,之后往高地址开始处理。这是因为src的低地址和dst的高地址是重叠的。对源地址src,如果直接从src的高地址开始拷贝,在拷贝完成前,会把src指向的内存数据污染掉。
即,对于重叠的区域,总是先处理重叠的区域,先把重叠区域的内容拷贝出来。
综合上述描述可知,
在(1)(2)(4)中的情况,可以使用对dst进行低地址往高地址填充。
其中,(1)src >= dst + count(4)dst < src && src < dst + count 等价于 程序中的pSrc >= pDst 。
(2)dst >= src + count 等价于程序中的 pDst >= pSrc + count
在(3)中的情况,可以使用对dst进行高地址往低地址填充。其对应着程序中的else的情况。
代码:
#include <iostream>
#include <assert.h>
using namespace std;
void* Memmove(void* dst,const void* src,size_t count)
{
assert(dst != NULL && src != NULL && count > 0);
char* pDst = static_cast<char*>(dst);
const char* pSrc = static_cast<const char*>(src);
if (pSrc >= pDst || pDst >= pSrc + count)
{
//对于目的地址dst,从起始地址dst到(dst + count - 1)依次进行填充
while (count--)
{
*pDst = *pSrc;
pDst++;
pSrc++;
}
}
else
{
//对于目的地址dst,从起始地址(dst + count - 1)到dst依次进行填充
pSrc = pSrc + count - 1;
pDst = pDst + count - 1;
while (count--)
{
*pDst = *pSrc;
pDst--;
pSrc--;
}
}
return dst;
}
int main()
{
char arrOne[10] = {'1','2','3','4','5'};
char arrTwo[10] = {'1','2','3','4','5'};
char arrTh[10] = {'1','2','3','4','5'};
char arrFor[10] = {'1','2','3','4','5'};
cout<<"--调用系统的函数---"<<endl;
cout<<arrOne<<endl; //12345
memmove(arrOne + 1,arrOne,4);
cout<<arrOne<<endl; //11234
cout<<arrOne + 1<<endl;//1234
/*分析:此时应该从目的地址的尾部开始拷贝,一直到首地址结束,从而得到我们想要的结果*/
cout<<"--调用系统的函数---"<<endl;
cout<<arrTwo<<endl; //12345
memmove(arrTwo,arrTwo + 1,4);
cout<<arrTwo + 1<<endl;//3455
cout<<arrTwo<<endl; //23455
/*分析:此时应该从目的地址的首地址开始拷贝,一直到尾部结束,从而得到我们想要的结果*/
cout<<"--调用手写的函数---"<<endl;
cout<<arrTh<<endl; //12345
Memmove(arrTh + 1,arrTh,4);
cout<<arrTh<<endl; //11234
cout<<arrTh + 1<<endl; //1234
/*分析:这个结果是我们想要的结果,执行else中的程序*/
cout<<"--调用手写的函数---"<<endl;
cout<<arrFor<<endl; //12345
Memmove(arrFor,arrFor + 1,4);
cout<<arrFor + 1<<endl;//3455
cout<<arrFor<<endl; //23455
/*分析:这个结果是我们想要的结果,执行if中的程序*/
system("pause");
return 1;
}
注意:
(1)从结果看,在VS2008编译器下,调用系统提供的函数memmove做了内存重叠检测。但是系统提供的函数memmove在内存拷贝时,可能会出现源地址内容改变的情况。这会是一个隐含的问题,也就是说,执行过这个函数memmove后,只能保证目的地址执行的内容是正确的,但是源地址执行的内容可能会改变,此时写程序要注意下。
(2)在单步执行看代码时,memmove直接优化成汇编代码了。由于看不到源码,不知道和本文写的思路是不是一样。
(3)在面试写这个代码时,还是要问清楚是否要考虑内存重叠以及源地址是否能够改变的问题。
存在的问题:
在网站 http://www.cplusplus.com/reference/cstring/memmove/ 说:
Copies the values of num bytes from the location pointed by source to the memory block pointed by destination.Copying takes place as if an intermediate buffer were used, allowing the destination and source to overlap.
能不能这样理解,由于使用了中间数组,是不会导致源地址内存内容的改变呢?和VS中的那个函数不同?求路过大牛解释。