memcpy和memmove函数的理解和实现

我们都知道strcpy函数可以将字符串进行复制,那么其他数据类型是否也有函数可以进行复制。我今天讲的就是可以进行多种类型复制的函数memcpy。

memcpy

 先看一下它的介绍,它的功能是拷贝内存块,把sourc指针指向的num个字节的数据拷贝到destination。再看一下它的定义,里面的参数,用到的是void*,这是为什么呢?

是因为这个函数的作用就是可以任意数据类型进行复制的,如果你不用void*去接收,用其他的都会限制住可以进行复制的数据类型。void*之所以可以是因为它是通用的类型指针,可以接收任意类型地址。后面的size_t就是用来接收传入的字节大小。之所以要传入字节大小的原因是跟为什么使用void*是一样的。

说完这些,让我们来自己一步步实现一下这个函数。

首先我们要知道void*虽然是可以接收任意类型指针地址,很好用,但是相对的,它无法进行解引用的操作和++,--。就是如下代码进行的操作是不行的。

void* my_memcpy(void* dest, const void* src, size_t num)
{
    *dest == *src;
    dest++;
    src++;
}

因为你根本不知道它接收的数据是什么类型的数据,无法确定上述操作一次该取几个字节,所以我们需要要进行强制类型转换,那么我们该强制类型转换成哪个类型呢?我们知道还有个形参num是要复制的字节个数,我们无法确定数据类型,不知道它传进来的字节大小。如果我强制类型转化成int*,每次四个字节进行复制,如果它传进来个5,这就很尴尬了,++之后多出三个字节,所以我们把精度放到最细,一个字节一个字节的复制过去不就实现了吗,而char类型的字节大小正好是1,所以我们就强制类型转换成char*。

我们把代码进一步修改

void* my_memcpy(void* dest, const void* src, size_t num)
{
    *(char*)dest == *(char*)src;
    dest++;
    src++;
}

大家觉得这样行了吗,如果觉得行的话,那你可能不知道强制类型转化是临时的,到dest++时,dest还是void*,无法进行++操作,那我们再改一下代码。

void* my_memcpy(void* dest, const void* src, size_t num)
{
    *(char*)dest == *(char*)src;
    (char*)dest++;
    (char*)src++;
}

这样我们把dest和src都强转,这样行吗,还是不行的,这样的数据还是临时的。那我再改。

void* my_memcpy(void* dest, const void* src, size_t num)
{
    *(char*)dest == *(char*)src;
    ((char*)dest)++;
    ((char*)src)++;
}

这样总行了吧,这样的代码不好说每次都对,所以我们还是换一种写法。

void* my_memcpy(void* dest, const void* src, size_t num)
{
    *(char*)dest == *(char*)src;
    dest = (char*)dest + 1;
    src = (char*) src + 1;
}

这样就好多了,将dest强转之后加一跳过一个字节,再用dest接收,因为dest是void*,什么类型地址都可以接收,所以这样就实现每次跳过1个字节。

最终实现的代码:

void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* p = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return p;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8 };
	int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 20);
	for (int i = 0; i < 5; i++)
	{
		printf("%d", arr2[i]);
	}
	return 0;
}

运行结果: 

 实现也讲完了,再讲讲它的一些使用。

我们知道它可以实现两个数组的复制,那如果它要实现数组内部的复制,又是否可以实现。

void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* p = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return p;
}

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memcpy(arr1 + 2, arr1, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d", arr1[i]);
	}
	return 0;
}

来看看这个更改了一下的代码,按照我们之前的逻辑,它的打印结果是不是应该是1,2,1,2,3,4,5,8,9,10。那实际上是不是呢,来看一下结果:

 memmove

很奇怪,为什么结果是1 2 1 2 1 2 1 8 9 10,这里我们引用一个新的函数memmove。用来实现同组数据的复制。我们来仔细想一下。当我们运行程序之后,我们的复制从3开始复制,这个时候arr1还是不变的,开始复制之后,3变成了1,4变成了2。这个时候arr1已结变成了1,2,1,2,5,6,7,8,9,10。下一步dest指向了5,而src的指针指向的数字的第三个数字:1,在我们原本的想象中,这个时候我的src指针应该指向的是3,然后把5变成3。而现在的结果是5变成了1。原因就是在我们进行复制操作的时候,改变了arr1,所以复制的结果才会发成改变。那我们是否可以规避掉这个风险,这个就是memmove的作用了。让我们来一步步分析这个问题。

 如图,红色框框代表的是我要覆盖掉的内容,蓝色框框是我要进行复制的内容,现在的问题是当我复制到3的时候,3已经被改变了,造成复制完的结果并不是我想要的结果。如果我们想解决这个问题也简单。既然正序不行,我们倒着来试试看。

首先把5复制到7,4复制到6,3复制到5,2->4 1->2。是不是完美解决问题,不会造成上述的问题。但是,这样就结束了吗,我们每次遇到全部都变成从后往前拷贝,这样行吗?

来看另一种复制要求:

复制与被复制的内容交换了,我们再来看看倒着拷贝是否还可行。7->5  6->4  到这一步走到5这里,5已经被改变了,所以这个时候不能倒着拷贝了。但是你可以发现正着拷贝又不会出错了。这个时候你应该知道,我们应该分情况去讨论了。

 不难发现从哪里开始拷贝好像跟dest和src的位置有关系,我们知道数组的地址随着下标增加,由低到高开始变化,所以:

                                      

 这个时候dest落在src左边,是从前->后,那我们可以总结一下:

                                      

我把它先分成三个区域,分别是dest落在要复制的内容的左边,里面,右边。已知落在左边是前到后,中间是后到前,而在右边因为无接触,所以从前到后,从后到前都可以,所以为了方便,我们就当做只有两种情况,一种是dest在src的右边,一种是在左边,在左边是前到后,右边是后到前。分析到这里可以开始写代码了。

我们再来理一下思路,首先看第一种情况从前向后拷贝,是不是就跟我们之前memcpy一样,这个不需要改变。重要的是从后向前,从后向前我们首先要找到复制的内容的最后一个数据,从前到后是++,从后到前--就好了,而且不用自己--,跟num结合起来:

		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}

num进来变成19,dest + 19就是第20个字节,这样第20个字节就完成交换,然后num=19满足循环条件,进入循环变成18。以此类推,完成全部字节的交换。

最终memmove实现代码:

void* my_memmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* p = dest;
	if (dest < src)
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return p;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 0 };
	my_memmove(arr1 + 2, arr1, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

实现结果: 

 

 这样子实现可以避免再创建一个空间。

最后有一点需要说明一下,可能在你的编译器里memcpy可以实现重叠的数据复制,但还是最好使用memmove,因为并不是所有的编译器的memcpy都可以实现重叠的数据复制。当然统一用memmove函数也好。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值