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

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

  • 32
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中,我们可以模拟实现memcpy函数memcpy函数的原型为void * memcpy(void * destination, const void * source, size_t num),它的作用是将源地址(source)指向的内存块的数据拷贝到目标地址(destination)指向的内存块中,拷贝的字节数由第三个参数num指定。 要实现memcpy函数,我们可以按照以下步骤进行操作: 1. 首先,我们需要创建一个与源内存块相同大小的目标内存块,并将目标内存块的地址保存在destination指针中。 2. 接下来,我们使用一个循环来逐字节地将源内存块的数据复制到目标内存块中,直到拷贝的字节数达到了num的值。 3. 最后,我们返回目标内存块的地址(destination指针),作为函数的返回值。 下面是一个示例实现代码: ```c void *my_memcpy(void *destination, const void *source, size_t num) { char *dest = (char *)destination; const char *src = (const char *)source; for (size_t i = 0; i < num; i++) { dest[i = src[i]; } return destination; } ``` 在这个实现中,我们将destination和source指针都转换为char类型的指针,这样可以逐字节地进行数据拷贝。然后,我们使用一个循环来逐字节地将源内存块的数据复制到目标内存块中,直到拷贝的字节数达到了num的值。最后,我们返回目标内存块的地址作为函数的返回值。 这样,我们就实现了一个简单的memcpy函数模拟实现。需要注意的是,这只是一个简单的示例实现,实际的memcpy函数可能会有更复杂的实现方式,具体实现可能因编译器和操作系统的不同而有所差异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值