注:这里给小伙伴们一些建议,看API文档的时候,一定要看全英,在本篇文章,我会带领大家看memcpy和memmove的全英解析,并翻译给大家。
目录
1. memcpy函数
1.1 函数的声明
1.2 函数的功能
1.3 函数的使用
1.4 函数的模拟实现(重点)
1.4.1 模拟分析
1.4.1 模拟实现
2. memmove函数
2.1 函数的声明
2.2 函数的功能
2.3 函数的使用
2.4 函数的模拟实现(重点)
2.4.1 模拟分析
2.4.2 模拟实现
1. memcpy函数
1.1 函数的声明
void * memcpy ( void * destination, const void * source, size_t num );
- destination
destination指向的是接收数据的目标,其类型转化为void*类型。
- source
source指向提供数据复制的源头, 其类型转化为void*类型。
- num
按字节为单位进行复制,size_t类型是无符号的整型类型
将destination指向的地址返回
1.2 函数的功能
- 第一段
按字节为单位,从source位置开始将多少个字节数的数据复制给destination的内存块中。
- 第二段
source和destination指针指向的底层数据类型和函数无关,结果是以二进制进行数据复制。
- 第三段
函数不会检查是否有null或者'\0'在source中,它总是会准确复制num个字节数的数据。
- 第四段
为了避免溢出,destination和source各自指向的数组大小至少是移动num个字节的大小,且不能出现destination和source指向的位置重叠(如果想要重叠,memmove是一个更安全的方法)。
1.3 函数的使用
/* memcpy example */
#include <stdio.h>
#include <string.h>
struct {
char name[40];
int age;
} person, person_copy;
int main ()
{
char myname[] = "Pierre de Fermat";
/* using memcpy to copy string: */
memcpy ( person.name, myname, strlen(myname)+1 );
person.age = 46;
/* using memcpy to copy structure: */
memcpy ( &person_copy, &person, sizeof(person) );
printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
return 0;
}
1.4 函数的模拟实现(重点)
1.4.1 模拟分析
(1)首先,我们不知道要被复制的数据是什么类型,所以参数只能是void*来接收数据,既然不知道是什么数据类型,所以我们也不清楚指针要怎么跳跃去复制,所以只能一个一个字节去复制,这就必须在函数体内把destination和source指向强转成char*(这里就会有同学问了,为什么能直接用char*接收呢?答:因为char*只能接受char*的指针,而void*类型虽然不能直接访问,但是可以接收任何类型的指针)。且观察mencpy,发现需要一个目标指针destination、源头指针source和字节个数size_t num。
(2)source指针指向的内容是不需要改变的,所以我们可以加上const修饰,把里面的数据保护起来(const void* source)。
(3)为了实现链式访问,我们要将传进来的目标起始地址(destination)返回。由于这个函数在执行的时候会改变destination存储的内容,所以我们要重新创建一个void*类型的指针来代替destination指针移动。
(4)为了避免传进来的地址是空指针,我们需要用assert来断言传进来的地址不是空指针。
1.4.1 模拟实现
//自我实现memcpy的功能
void* my_memcpy(void* destination, const void* source, size_t num) {
//先判断destination和source是不是为空
assert(destination && source);
void* tmp = destination;
while (num--) {
//记住强转数据类型并不会永久改变变量的数据类型
*(char*)tmp = *(char*)source;
((char*)tmp)++;
((char*)source)++;
}
return destination;
}
int main() {
int a[4] = { 0 };
int b[4] = { 1,2,3,4 };
int num = sizeof(b);
my_memcpy(a, b, num);
for (int i = 0;i < 4;i++) {
printf("%d ", a[i]);
}
return 0;
}
2. memmove函数
2.1 函数的声明
void * memmove ( void * destination, const void * source, size_t num );
- destination
destination指向的是接收数据的目标,其类型转化为void*类型。
- source
source指向提供数据复制的源头, 其类型转化为void*类型。
- num
按字节为单位进行复制,size_t类型是无符号的整型类型。
将destination指向的地址返回 。
2.2 函数的功能
- 第一段
按字节为单位,从source位置开始将多少个字节数的数据复制给destination的内存块中。复制就像使用了中间缓冲区一样,允许重叠复制。
- 第二段
source和destination指针指向的底层数据类型和函数无关,结果是以二进制进行数据复制。
- 第三段
函数不会检查是否有null或者'\0'在source中,它总是会准确复制num个字节数的数据。
- 第四段
为了避免溢出,destination和source各自指向的数组大小至少是移动num个字节的大小。
2.3 函数的使用
/* memmove example */
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "memmove can be very useful......";
memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
2.4 函数的模拟实现(重点)
2.4.1 模拟分析
- memcpy和memmove的比较
为什么需要memmove?就得了解memcpy和memmove的区别!
这个还要从上面的memcpy函数说起。因为memcpy函数不能将一个数组的中的数据拷贝到自身(也就是目标数据是自己,源数据也是自己,只不过是一个数组里面不同的位置的数据拷贝到另外一个位置上),如果像这样拷贝就会出现重叠拷贝,会导致结果不是我们预期的结果。
//使用我们自己的模拟的memcpy函数
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
my_memcpy(arr + 2, arr, 24);//预期出现结果为1 2 1 2 3 4 5 6 9 10
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//实际出现结果
}
return 0;
}
- 细节分析
2.4.2 模拟实现
//自我实现memmove的功能
void* my_memmove(void* destination, const void* source, size_t num) {
//先判断destination和source是不是为空
assert(destination && source);
void* tmp = destination;
//从前往后走
if (destination < source) {
while (num--) {
*(char*)tmp = *(char*)source;
((char*)tmp)++;
((char*)source)++;
}
}
//从后往前走
else {
while (num--) {
*((char*)tmp + num) = *((char*)source + num);
}
}
return destination;
}
int main() {
int b[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(b, b + 3, 16);//b+3:是从4开始,预期结果:4,5,6,7,5,6,7,8,9,10
for (int i = 0;i < 10;i++) {
printf("%d ", b[i]);
}
printf("\n");
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(a+2, a, 24);//a+3:是从4开始,预期结果:1,2,3,1,2,3,4,8,9,10
for (int i = 0;i < 10;i++) {
printf("%d ", a[i]);
}
}