注:这里给小伙伴们一些建议,看API文档的时候,一定要看全英,在本篇文章,我会带领大家看memcpy和memmove的全英解析,并翻译给大家。
目录
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]);
- }
- }