首先模拟实现memmove函数之前,我们需要先明白这个函数的功能和用法:memmove用于从src拷贝count个字节到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。也就是说,他可以处理memcpy函数不能处理的如果拷贝时出现重叠的情况。那么下面我们就来具体说说怎么模拟实现memmove函数。
我们要知道当内存拷贝时分为两种情况,看下面这幅图
如图片中所看到的,第一种情况是当目标指针变量dest的起始位置小于源指针变量src的起始位置,这时候我们需要将src指向的内容正着拷贝到dest所指向的空间,即从左向右依次拷贝。第二种情况是当目标指针变量dest的起始位置大于源指针变量src的起始位置,需要将src指向的内容倒着拷贝到dest所指向的空间,即从右向左依次拷贝。如果不这样,很有可能产生覆盖。
下面是我们的具体实现代码,里面有比较详细的解释,供大家参考:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*基本思想:为了避免重叠,分为两种情况。如果目标指针变量dest的起始值小于源指针变量src的起始值,那么正着拷贝,即
从左向右拷贝。如果目标指针变量dest的起始值大于源指针变量src的起始值,那么倒着拷贝,即从右向左拷贝*/
void* my_memmove(void* dest, const void*src, int count)//因为是内存拷贝函数,所以什么类型都需要接收,所以此处用void*做函数返回值类型和参数类型
{
assert(dest != NULL);
assert(src != NULL);
void* ret = dest;//因为dest在循环体内是要变化的,所以定义一个临时指针变量来存放dest的地址。dest变化会引起ret变化,但ret变化不会引起dest变化
if (dest < src)//如果dest的起始位置小于src的起始位置,那么从左向右进行拷贝
{
while (count--)
{
*(char*)dest = *(const char*)src;//因为void*类型不能进行解引用操作,所以要进行强制类型转化,并且count是代表的是字节数,一个字节一个字节的拷贝,所以强制转化为char*类型
++(char*)dest;//因为void*类型不能进行++运算,所以进行强制类型转化,并且如果是后置++,那么强制类型转化会先对dest进行,所以要进行前置++
++(char)src;
}
}
else
{
*((char*)dest + count) = *((const char*)src + count);//从右向左拷贝
--(char*)dest;
--(char*)src;
}
return ret;
}
//打印输出arr函数
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
my_memmove(arr, arr + 3, 16);//此处的16为字节数
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
system("pause");
return 0;
}
运行的结果如下图:
最后再说几点需要注意的地方:
1.dest和src指针变量需要断言,不能为空。并且源指针变量src是不改变的,所以要用const修饰,以起到保护作用。
2.因为是内存拷贝函数,所以函数的返回值类型和参数类型都必须可以接收任何类型,所以要使用void*做函数的返回值类型和参数类型。
3.void*类型是不能进行解引用操作和++、–运算的,所以在进行解引用操作和++、–运算之前要进行强制类型转换,并且,memmove函数是一个字符一个字符拷贝,所以这里要强制类型转换为char*类型。
4.因为目标指针变量dest在循环体内是要发生改变的,所以定义一个临时指针变量来存放dest的地址,此后dest变化临时指针变量就会变化,但临时指针变量变化不会引起dest变化,最后函数返回临时指针变量即可。