一、memcpy()
函数原型
void * memcpy ( void * dest, const void * src, size_t num );
参数说明
函数 memcpy 从 src 位置开始向后复制 num 个字节的数据到 dest 的内存位置。
void * dest 代表目标的内存地址,const void * src 代表源内存地址。其中二者的数据类型均为 void * 。void * 可以存储任何类型地址的值。因此,该函数拷贝的内存数据可以是任意类型(如果int、float、double等均可)。
size_t 即unsigned int类型。注意:num代表的是要拷贝的字节数,而非元素个数!如要从src拷贝一个int类型的数据到dest,则num应传入4,而非1.
该函数的返回值类型也为 void * ,也即只返回目标地址的数值。后续如何按照基类型取出数据、使用数据,可以由调用方在调用函数后实现。
模拟算法
void* my_memcpy(void* dest, const void* src, size_t num) { void *ret = dest; //保存dest,用于最终输出 assert(dest); assert(src); while (num--) { *(char*)dest = *(char*)src1; //(char*): 取出每一个字节(8 bit)的值,以单个字节为单位进行赋值 dest = (char*)dest + 1; src = (char*)src + 1; } //void * 类型是不能直接运算的,因为没有步长。(char*)将dest与src转换为以char为步长,再向后移动 return ret; }
- 该函数用于实现没有重叠部分内存的内存数据拷贝。如果source和destination有任何的重叠,复制的结果都是未定义的。
- 拷贝内存值按从低地址到高地址的顺序进行,多用于数组。
- 根据memcpy()的模拟算法,如果src与dest的内存空间有重叠部分,则可能导致src中的内容被覆盖,无法输出正确的值。
src中元素3的位置恰好也是dest中首元素的位置。dest的首元素被更改的同时,src中的元素也被更改。因此,memcpy()是不可用于“自己拷贝到自己后面”这样的操作的。这一问题留给了memmove()来解决。
使用示例
1.简单
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 0 }; memcpy(arr2, arr1, 20); //拷贝20个字节,即5个int元素 float arr3[] = { 1.0f,2.0f,3.0f,4.0f }; float arr4[5] = { 0.0 }; memcpy(arr3, arr4, 8); //拷贝8个字节,即2个float元素 return 0; }
2.进阶
//示例来自cplusplus官网 /* memcpy example */ #include <stdio.h> #include <string.h> struct { char name[40]; int age; } person, person_copy; int main () { char myname[] = "Pierre de Fermat"; //定义一个字符串 /* 用 memcpy 拷贝字符串 */ //每个char类型占一个字节,因此要拷贝的字节数即strlen()+1,加一是因为要把'\0'也拷贝过去。 memcpy ( person.name, myname, strlen(myname)+1 ); person.age = 46; /* 用 memcpy 拷贝结构体 */ //sizeof操作符,可以直接得到结构体变量在内存中所占的字节数。 memcpy ( &person_copy, &person, sizeof(person) ); //直接完成了结构体之间的数据拷贝:从person拷贝到person_cpy,不用手动转义,非常方便 printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age ); return 0; }
二、memmove()
函数原型
void * memmove ( void * dest, const void * src, size_t num );
参数说明
- 该函数的参数与返回值类型与memcpy()函数相同。该函数同样用作 从 src 位置开始向后复制 num 个字节数据到 dest 的内存位置。
- memmove()函数与memcpy()函数主要的区别在于memmove()可以进行有内存重叠的数据拷贝,而memcpy()绝对不能。memmove()的功能比memcpy()更加完善。
- 可以理解为:如果memcpy()函数够到了60分,那么memmove()函数却能到达90分。
模拟算法
#include<stdio.h> #include<string.h> //模拟实现memmove() void* my_memmove(void* dest, const void* src, size_t num) { void* ret = dest; //保存结果用于输出 //从前向后拷贝,也可以写成if(dest <= src || (char*)dest >= (char*)src + num) if (dest <= src) { while (num--) { *(char*)dest = *(char*)src; //拷贝 dest = (char*)dest + 1; src = (char*)src + 1; //指针从前向后移动(从低地址向高地址移动) } } else //从前向后拷贝 { dest = (char*)dest + num - 1; src = (char*)src + num - 1; //初始化两指针至各自范围的最后 while (num--) { *(char*)dest = *(char*)src; //拷贝 dest = (char*)dest - 1; src = (char*)src - 1; //指针从后向前移动 } } return ret; //返回值为dest } //测试代码/// int main() { int arr1[] = { 2,3,4,5,6 }; my_memmove(arr1+2, arr1, 8); //预计 2 3 2 3 6 for (int i = 0; i < 5; i++) { printf("%d ", arr1[i]); } printf("\n-------------------\n"); int arr2[] = { 2,3,4,5,6 }; memmove(arr2 + 2, arr2, 8); for (int i = 0; i < 5; i++) { printf("%d ", arr2[i]); } return 0; }
拷贝顺序的结论如图所示。上面提到,当有内存重叠时,拷贝的顺序是有讲究的。若不遵守下图的结论, 仍将导致src中原来的值被覆盖,无法输出正确的结果。
***错误的模拟算法
如下代码是错误的:
//错误的代码 void* my_memmove(void* dest, const void* src, size_t num) { void * ret = dest; while (num--) { //从前向后拷贝 if (dest <= src) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } else { dest = (char*)dest + num - 1; //错误 src = (char*)src + num - 1; //错误 *(char*)dest = *(char*)src; dest = (char*)dest - 1; src = (char*)src - 1; } return ret; }
有一些同学可能认为,while(num--)语句与if语句的顺序可以调换。正确的代码是先进行情况判断,再进入while(num--)进行赋值与移动。将二者顺序调换,乍一看没有什么问题,是先设定一共要移动num次,再进入判断进行具体的操作。但是这样书写是有问题的,因为在dest > src && dest < src+num时,需要从后向前拷贝,这意味着dest和src的起始位置要发生变化。
上述代码中标记“错误”的语句便是dest和src初始化的语句。如果直接将while和if的位置调换,则每一次进入循环,都要初始化一遍dest和src。如此,dest与src的功能就被打乱了。
使用示例
//示例来自cplusplus官网 /* memmove example */ #include <stdio.h> #include <string.h> int main () { char str[] = "memmove can be very useful......"; memmove (str+20,str+15,11); //表示将包括str+15向后11个字节的内容移动到str+20位置 puts (str); return 0; }
输出:
如下图,将 src = str+11 位置开始,包括该位置共向后拷贝11字节。每个char占一个字节,因此拷贝了"very useful"这7个char字母至dest = str+20的位置。