注:这里给小伙伴们一些建议,看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 模拟实现
void * memcpy ( void * destination, const void * source, size_t num );
destination指向的是接收数据的目标,其类型转化为void*类型。
source指向提供数据复制的源头, 其类型转化为void*类型。
按字节为单位进行复制,size_t类型是无符号的整型类型
将destination指向的地址返回
按字节为单位,从source位置开始将多少个字节数的数据复制给destination的内存块中。
source和destination指针指向的底层数据类型和函数无关,结果是以二进制进行数据复制。
函数不会检查是否有null或者'\0'在source中,它总是会准确复制num个字节数的数据。
为了避免溢出,destination和source各自指向的数组大小至少是移动num个字节的大小,且不能出现destination和source指向的位置重叠(如果想要重叠,memmove是一个更安全的方法)。
/* memcpy example */
#include
#include
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)首先,我们不知道要被复制的数据是什么类型,所以参数只能是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来断言传进来的地址不是空指针。
//自我实现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;
}
void * memmove ( void * destination, const void * source, size_t num );
destination指向的是接收数据的目标,其类型转化为void*类型。
source指向提供数据复制的源头, 其类型转化为void*类型。
按字节为单位进行复制,size_t类型是无符号的整型类型。
将destination指向的地址返回 。
按字节为单位,从source位置开始将多少个字节数的数据复制给destination的内存块中。复制就像使用了中间缓冲区一样,允许重叠复制。
source和destination指针指向的底层数据类型和函数无关,结果是以二进制进行数据复制。
函数不会检查是否有null或者'\0'在source中,它总是会准确复制num个字节数的数据。
为了避免溢出,destination和source各自指向的数组大小至少是移动num个字节的大小。
/* memmove example */
#include
#include
int main()
{
char str[] = "memmove can be very useful......";
memmove(str + 20, str + 15, 11);
puts(str);
return 0;
}
为什么需要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;
}
//自我实现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]);
}
}