问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言进阶——memcpy函数的使用与模拟实现

创作时间:
作者:
@小白创作中心

C语言进阶——memcpy函数的使用与模拟实现

引用
CSDN
1.
https://m.blog.csdn.net/2301_77078067/article/details/136585809

memcpy函数是C语言中用于内存拷贝的重要函数,它能够将一块内存区域的内容复制到另一块内存区域。本文将详细介绍memcpy函数的声明、作用、使用方法,并通过代码示例展示其工作原理。此外,本文还将介绍如何模拟实现memcpy函数,帮助读者深入理解其内部机制。

一、函数的声明

void * memcpy ( void * destination, const void * source, size_t num );
  1. 函数的第一个参数为:指向目标空间(destination)的指针
  2. 函数的第二个参数为:指向源头空间的指针
  3. 函数的第三个参数为:无符号整型类型的num
  4. 函数的返回值类型为: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*类型的指针

  1. 不能对void*指针进行加减操作。

  2. 不能对void*指针进行解引用操作。

  3. 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,依次类推...

好啦这就是本篇文章的所有内容了,感谢你的观看!

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号