03-C语言进阶——模拟实现内存操作函数(memmove与memcpy)


memcpy和memmove并不属于字符串操作函数,而是内存操作函数,而对内存进行复制和移动会存在多种情况,下面通过模拟实现两种函数来讨论两块内存的不同情况和内存操作函数的不同之处,该节内容难以用文字表述清楚,如有问题或不明白的地方可以留言讨论!

1.memcpy与memmove区别

首先查阅c语言官方文档,辨析两个函数的区别,英语用翻译软件翻译可以直接看总结
在这里插入图片描述
翻译:

  • 将num字节的值从源指向的位置直接复制到目标指向的内存块。
  • 源指针和目标指针所指向的对象的底层类型与此函数无关;结果是数据的二进制副本。
  • 该函数不检查源文件中的任何终止null字符-它总是准确地复制num字节。
  • 为了避免溢出,目标和源参数所指向的数组的大小应该至少为num字节,并且不应该重叠(对于重叠的内存块,memmove是一种更安全的方法)。
    在这里插入图片描述
    翻译:
  • 将num字节的值从源指向的位置复制到目标指向的内存块。复制就像使用了中间缓冲区一样进行,允许目标和源重叠。
  • 源指针和目标指针所指向的对象的基础类型与此函数无关;结果是数据的二进制副本。
  • 该函数不检查源文件中的任何终止null字符-它总是准确地复制num字节。
    为了避免溢出,目标和源参数所指向的数组的大小应该至少为num字节。
    总结:简单来说,二者的区别就是memcpy不考虑内存重叠情况,而memmove考虑内存重叠。如下所示为内存块的几种重叠方式
    在这里插入图片描述

2.模拟实现memcopy

首先构造函数

  • memcpy返回类型为void *
  • 将src拷贝给dst,因此src为const类型
//1.模拟实现memcpy
void * my_memcpy(void * dst, const void * src, size_t num)
{
    
    
	//判断指针合法性
	assert(dst != NULL);
	assert(src != NULL);

	//将void *强制类型转为char *,方便按字节拷贝
	char *_dst = (char *)dst;
	const char * _src = (const char *)src;

	//拷贝num个字节
	while (num)
	{
    
    
		*_dst = *_src;
		_dst++;
		_src++;
		num--;
	}

	return dst;


}

该函数无法应对内存重叠情况,如下所示

int main()
{
    
    
	//内存重叠
	char buf[16] = "abcdef";
	my_memcpy(buf + 1, buf, strlen(buf)+1);
	printf("%s\n", buf);
	system("pause");
	return 0;
}

结果
在这里插入图片描述
拷贝出现全a情况是因为从左向右拷贝时,每次左边的字符都会被a覆盖。
在这里插入图片描述

3.模拟实现memmove

在这里插入图片描述
内存重叠会出现上面四种情况:
1.dst的起始位置在src起始位置的左边,并且两块内存大小相等,从左向右拷贝时,每次src前一个字符都会被拷贝给dst字符,因此不会出现被同一个字符覆盖情况。
2.dst的起始位置在src起始位置的左边,并且dst的内存块大于src的内存块,从左向右拷贝时,每次src前一个字符都会被拷贝给dst字符,因此也不会出现被同一个字符覆盖情况。
3.dst的起始位置在src起始位置的右边,并且dst的内存块大于或者等于src的内存块从左向右拷贝时,src的第一个字符a会重复覆盖dst的每一个字符,最终出现全a的情况。因此这种情况需要从右向左拷贝!
4.dst内存块小于src内存块,此种情况不会出现,因为目标内存小于拷贝的内存,发生溢出!
总结:

  • 1,2可以看做一种情况,因为从左向右拷贝不会出现问题
  • 3可以看做一种情况

实现思路:
在这里插入图片描述
假设左边为低地址,右边为高地址,那情况3就可以这样表示

  • dst > src && src+len(src)
    • dst > src,表示dst的起始位置在src起始位置的右边
    • dst < src+len(src), 表示dst的起始位置小于src末尾位置
    • && 两种情况用逻辑与表示dst的起始位置一定在src起始位置与末尾位置的中间,简单来说就是dst与src存在交集!!!。而在使用内存操作函数时,dst的空间一定是大于等于src空间的,因此不用担心dst的整个空间落在src的空间内,也就是下图情况不会出现!
      在这里插入图片描述
      代码实现
//2.模拟实现memmove
void *my_memmove(void *dst, const void *src, size_t num)
{
    
    
	//判断指针合法性
	assert(dst != NULL);
	assert(src != NULL);

	//将void *强制类型转为char *,方便按字节拷贝
	char *_dst = (char *)dst;
	const char * _src = (const char *)src;

	//情况3从右向左拷贝
	if (_dst > _src && _dst < _src + num)
	{
    
    
		//首先让dst与src指向最右边
		_dst = _dst + num - 1;
		_src = _src + num - 1;

		//拷贝num个字节
		while (num)
		{
    
    
			*_dst = *_src;
			_dst--;
			_src--;
			num--;
		}
	}
	//其他情况全部从左向右拷贝
	else
	{
    
    
		//拷贝num个字节
		while (num)
		{
    
    
			*_dst = *_src;
			_dst++;
			_src++;
			num--;
		}
	}

	return dst;
}

验证内存重叠

int main()
{
    
    
	//内存重叠
	char buf[16] = "abcdef";
	my_memmove(buf + 1, buf, strlen(buf) + 1);
	printf("%s\n", buf);
	system("pause");
	return 0;
}

结果
在这里插入图片描述
完美应对

4.实践检验库函数中内存操作函数是否有区别

博主使用win10 64位操作系统,VS2015验证
在这里插入图片描述
通过实验室验证如上图所示,vs2015版本的库函数memcpy和memmove没有区别,原因可能为memcpy函数已经被进行优化不会出现内存重叠出错情况!

猜你喜欢

转载自blog.csdn.net/qq_40076022/article/details/110497156