memcpy函数
一、函数的声明:
void * memcpy ( void * destination, const void * source, size_t num );
- 函数的第一个参数为:指向目标空间(destination)的指针
- 函数的第二个参数为:指向源头空间的指针
- 函数的第三个参数为:无符号整型类型的num
- 函数的返回值类型为:void*,返回的是目标空间的起始地址,如图:
接下来我给出一些解释:我们来看参数类型,destination 和 source的类型均为void*。
为什么这样设计呢?这是因为作为memcpy的设计者,他并不知道将来函数参数会得到一个什么类型的指针,有可能是char*,int*,所有的类型都是有可能的,所以这里设计为void*是非常合理的。
source前用const修饰,这是因为,内存拷贝,我们需要的只是原空间的数据,并不需要修改。
同理我们来看函数的返回值类型同样为void*,这是因为函数返回的是目标空间的起始地址,所以与destination指针的类型相同。
最后一个参数为无符号整型,size_t 整型永远 >0,这是因为num传入的是我们需要拷贝的字节数,我们不可能拷贝负数个字节数,所以这里用无符号整型。
二、函数的作用:
把source指针指向的空间拷贝到destination指针指向的空间去,拷贝num个字节,这里我们需要注意:内存函数操作的单位均为字节。
memcpy和字符串函数(例如:strcpy)不同,遇到\0并不会停止拷贝,它并不关注内存中存放的是什么,只是按照给定的字节数拷贝。
memcpy一般用于两个不发生重叠的空间的拷贝,如果内存空间发生重叠,使用memcpy函数的结果是不可控的,这是为什么呢?
三、函数的使用
下面我来应用一下memcpy函数:
int main()
{
int arr1[20] = { 0 };
int arr2[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//首先创建两个arr数组
memcpy(arr1, arr2, sizeof(arr2));
//将arr2中的数据拷贝到arr1中去
//调用函数后,我们用for循环打印arr1数组
//原来arr1数组中存放的是0
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
arr1中原来存放的都是0,调用memcpy后:
前10个元素都变成了arr2中的元素。
四、函数的模拟实现:
我们知道,memcpy函数是将原内存中空间进行拷贝,放到目标空间中去,我们前面提到,内存函数操作的单位均为字节。那么可以得出,memcpy是从原空间到目标空间的每一对字节进行拷贝,并且每次只操作一对字节(原空间和目标空间分别操作一个字节,不是同一空间的一对字节,这里不要混淆概念)。
下面我们来尝试模拟实现:
void* my_memcpy(void* dest, const void* src, size_t num)
函数的返回值以及参数部分,和memcpy保持一致即可,这里在重复一下src指向的内容不需要修改,所以加以const进行修饰
*(char*)dest = *(char*)src;
//将原内存空间的数据拷贝到目标空间中去
说明:
由于dest和src均为void*类型,所以在解引用时我们需要把他们两个强制类型转化为char*
至于为什么转化为char*,前面已经提到,memcpy只操作一个字节,char*恰好同样每次只访问一个字节,所以char*在合适不过了
把第一个字节拷贝后需要将dest和src分别向后移动一个字节:
但是不能直接对dest 和 src直接进行加减操作,切记,他们是void*类型的指针
void*类型的指针
- 不能对void*指针进行加减操作。
- 不能对void*指针进行解引用操作。
- void*指针可以接受任意类型的指针。
有同学可能会说,前面我们不是将dest 和 src 进行强制类型转化的操作了嘛,这里又不得不提到一个知识点:强制类型转化只是临时的,下一次使用的时候还是原来的类型,也就是说虽然进行了强制类型转化,dest 和 src 下次使用的时候依然是void*类型的指针。
于是我们再对dest 和 src强制类型转化,依然转化为char*,并且+1后,再赋值给dest 和 src , 这样就完成了void*类型的 dest 和 src 的+1 操作。
dest = (char*)dest+1;
src = (char*)src+1;
上面我们将一个字节的内容成功的拷贝过去,接下来只需要将这个过程重复n次,就可以达到memcpy函数的功能了。
void* my_memcpy(void* dest, const void* src, size_t num)
//源头不需要修改,加上const
{
while (num--)//将num作为判断条件,当num--等于0时,自动跳出循环
{
*(char*)dest = *(char*)src;//强制类型转化只是临时的,下一次使用的时候还是原来的类型
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
这样一来,my_memcpy,已经完成一大半了,下面我们需要考虑一下函数的返回值,由上文可知,memcpy返回的是目标空间的起始地址,那么返回dest不就可以了吗?
事实上是不行的,因为在拷贝过程中,dest已经被我们移动了,那我们应该怎么做呢?
void* my_memcpy(void* dest, const void* src, size_t num)
//源头不需要修改,加上const
{
void* ret = dest;
assert(dest && src);
//这里我们使用断言,保证传入的dest和src不为空指针
while (num--)//将num作为判断条件,当num--等于0时,自动跳出循环
{
*(char*)dest = *(char*)src;//强制类型转化只是临时的,下一次使用的时候还是原来的类型
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
其实只需要在移动dest之前将dest保存在ret指针内,然后在进行return ret就可以了。
以上就是memcpy的模拟实现了,相信阅读到这里,你也学会了memcpy的模拟实现了吧,那赶快去敲出你的“my_memcpy”吧!~
以下是my_memcpy函数的完整模拟实现代码
//memcpy 的模拟实现
#include <stdio.h>
#include <assert.h>//assert需要包含此头文件
void* my_memcpy(void* dest, const void* src, size_t num)//源头不需要修改,加上const
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;//强制类型转化只是临时的,下一次使用的时候还是原来的类型
dest = (char*)dest+1;
src = (char*)src+1;
}
return ret;
}
//int main()
//{
// int arr1[100] = { 0 };
// int arr2[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
// my_memcpy(arr1, arr2, sizeof(arr2));//将arr2中的数据拷贝到arr1中去
// int i = 0;
// for (i = 0; i < 10; i++)
// {
// printf("%d ", arr1[i]);
// }
// return 0;
//}
学会了memcpy的模拟实现我们再来看这段代码:
int main()
{
int arr[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
my_memcpy(arr+2, arr, 20);//将arr向后20个字节的数据拷贝到arr+2处
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们的预期是把arr数组的1,2,3,4,5拷贝到arr数组的34567处,拷贝后数组中存储的数字应该为:1,2,1,2,3,4,5,8,9,0
我们可以看到,结果确实和我们的预期相同,但是这样就真的可靠吗?
答案是否定的:
C语言标准规定memcpy函数只需要完成两个不重叠的空间的内存拷贝
只是VS编译器让memcpy更完善了一些,但是未来当切换到其他编译器的时候,很可能就会造成预期之外的结果。例如:
,这个结果是怎么得来的呢?
这是因为1 2 3 4 5 6 7 8 9 0,在拷贝的过程中是每次只操作一个字节,
把 1 拷贝到 3 处,2 拷贝到 4处,当拷贝3的时候,我们会发现3不见了,已经被1给覆盖了,所以只能把1拷贝到下一个位置,也就变成了 1 2 1 2 1 6 7 8 9 0
想把4拷贝到6的位置的时候,会发现4也被2给覆盖了,1212127890,依次类推...
好啦这就是本篇文章的所有内容了,感谢你的观看!