一 . 函数介绍
前面介绍了对memcpy的使用以及模拟实现 , 明白了memcpy能够对任意数据类型进行指定字节数进行拷贝 .那么 , 当遇到目标空间与源空间在同一数组内,也就是数组自己拷贝自己的情况 , 对于memcpy的模拟实现的代码则无法完成任务 .
在库函数的标准下 ,memcpy是可以完成数组重叠拷贝 , 而在库函数的规定中memcpy专门用于两个数组之间的拷贝 , memmove则用于数组重叠拷贝 .
所以,接下来针对memmove进行模拟实现
二 . 分析函数
2.1参数
首先在cplusplus网站搜索memcpy函数,可以看到对于该函数的定义.他的返回值是void * ,表示函数的返回值是任意类型的指针.
他有三个参数 ,分别对其解释代表的含义.
参数一 (void * dest) : 指向任意类型的指针 , 指向需要存入数据的目标空间.
参数二 (const void * src) : 指向不可修改的空间,指向被拷贝数据的地址.
参数三 (size_t num) : 传入的是需要拷贝的字节个数.
三 . 函数应用
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr, arr + 2, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
函数传入的参数表示的目的是 : 将 arr+2 指向的地址往后的20个字节 , 拷贝到 arr 指向的地址 .
运行结果 :
成功的将 3 4 5 6 7 拷贝到 1 2 3 4 5 的地址处.
四 . 模拟实现
4.1 逻辑梳理
使用memcpy模拟实现的逻辑 ,是从源空间的第一个字节 , 从前往后的依次拷贝到目标空间中 .所以在memmove的模拟实现中尝试着套用该逻辑
如图 , 蓝色代表源空间 , 绿色代表目标空间 , 按照从前往后的逻辑 . 将 1 拷贝到 3 的位置 ; 2 拷贝到 4 的位置 ; 将 3 拷贝到 4 的位置 以此类推 . 这是理想的状况 , 可事实并非如此 .仔细思考 , 当前两个数据拷贝完成后 , 3 和 4 位置的数据就已经发生了变化 , 变成了1212567 , 如果继续拷贝 , 会继续将 1 2 拷贝到 5 和 6的位置, 无法达到预期 , 所以这种逻辑行不通 .
那么 ,从前往后不行 , 就尝试从后往前 , 还是以上图为例 ,蓝色代表源空间 ,绿色代表目标空间 ,将 5 拷贝到 7的位置 ; 4 拷贝到 6 的位置 ; 3 拷贝到 5 的位置 ;以此类推 . 不难发现 , 当3 需要拷到5 的位置时 , 5 已经拷贝完成了 , 不会造成未拷贝的数据被覆盖 , 所以这种情况下需要从后往前进行拷贝 .
但并不是所有情况都适用从后往前 , 比如 , 将上图的源空间和目标空间交换 , 则需要从前往后进行拷贝 .
所以,在代码实现的过程中 ,需要对dest 和 src 进行比较 , 是否dest 大于 是src . 若dest大于src ,则需要从前往后拷贝 , 若dest小于src , 则从后往前拷贝 .另外一种情况,两个空间没有重叠的部分,则两种拷贝顺序方式都可以实现 .
了解完基本的实现逻辑 , 接下来进行代码的实现 .
4.2 代码实现
首先 , 从前往后
//前->后
int i = 0;
for (i = 0; i < sz; i++) //for循环,拷贝sz个字节
{
*(char*)dest = *(char*)src; //强转为char*类型,再解引用
//由于函数适用任意类型的数据,所以拷贝时需要以字节为单位进行拷贝
dest = (char*)dest + 1; //同理,转为char*,再跳过一个字节
src = (char*)src + 1;
}
其次 , 从后往前
//后->前
while (sz--)
{
*((char*)dest + sz) = *((char*)src + sz);
}
这种顺序下 , 是从空间的最后一个字节开始拷贝 , 所以需要找到空间末尾的前一个字节 , 就要先强转为char*类型 , 再加sz个字节 , 由于进入while循环需要判断sz--是否为真 , sz减少一个字节, 所以强转后直接加上sz再解引用,直接拿到最后一个字节的内容 , 再进行拷贝 .
这样,两种情况都写完了,最后加上细节部分 , 完整代码 :
void* my_memmove(void* dest, const void* src, size_t sz)
{
void* ret = dest; //创建返回值
assert(dest&& src); //断言dest和src不为空指针
if (dest < src)
{
//前->后
int i = 0;
for (i = 0; i < sz; i++)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//后->前
while (sz--)
{
*((char*)dest + sz) = *((char*)src + sz);
}
}
return ret; //返回ret
}
4.3 运行结果
从前往后
从后往前