当前位置: 首页 > news >正文

【C进阶】——内存操作函数memcpy、memmove、memcmp、memset详解及其模拟实现

这篇文章给大家介绍一些C语言4个常见的内存操作函数以及它们的模拟实现,一起来学习吧!!!

文章目录

    • 1.内存块拷贝函数——memcpy
      • 1.1 函数介绍
      • 1.2 memcpy的模拟实现
      • 1.3 思考
    • 2.移动内存块(可拷贝重叠内存块)——memmove
      • 2.1 函数介绍
      • 2.2 memmove的模拟实现
    • 3. Visual Studio 对memcpy的实现
    • 4. 内存比较函数—— memcmp
      • 4.1函数介绍
      • 4.2 memcmp的模拟实现
    • 5. 内存设置函数—— memset
      • 5.1函数介绍
      • 5.2 memset的模拟实现

1.内存块拷贝函数——memcpy

我们一起来认识一下:
在这里插入图片描述

1.1 函数介绍

看到memcpy的参数,大家有没有感到似曾相识呢?
是不是跟strncpy的参数比较相似啊,我们来对比一下:
在这里插入图片描述
看它们的前两个参数及返回类型,唯一的区别就是一个是char* ,而一个是void*。
因为strcpy是char *,所以strcpy只能拷贝字符类型的数据。
而memcpy是void *,我们知道void *可以接收任何类型变量的地址,因此,对于memcpy,不管内存块种放的是什么类型的数据,使用memcpy都可以拷贝(将source指向空间的内容拷贝到destination指向的空间中去),参数size_t num 则用来指定想要拷贝的数据的字节个数。

我们看一下cplusplus对于memcpy的介绍:
在这里插入图片描述

接下来我们一起来练习一下它的使用。
我们先来尝试一下拷贝整形数据:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,1,1,1,1,1,1,1 };
	int arr2[] = { 5,5,5,5,5 };
	memcpy(arr1, arr2, 20);
	return 0;
}

我们把arr2中的前20个字节的内容拷贝到arr1中。

看看arr1发生变化没:
在这里插入图片描述
当然拷贝任何类型的数据都是可以的,我们再来试一下浮点型:

int main()
{
	double arr1[] = { 1.0,1.2,1.3,1.4,1.5,1.6,1.8,1.9 };
	double arr2[] = { 8.8,7.5,6.3,9.9 };
	memcpy(arr1, arr2, 24);
	return 0;
}

把arr2中的前20个字节(3个double变量的大小)的内容拷贝到arr1中

在这里插入图片描述

1.2 memcpy的模拟实现

我们已经明白这个函数是怎么工作了,那现在我们就来模拟实现一下memcpy。
那我们应该怎么去实现呢?

其实思路很简单,我们的目的是把源空间的num个字节的内容拷贝到目标空间里,那我们就可以这样做:
使用一个while循环,让它循环num次,每次我们拷贝一个字节的内容。
那现在又有一个问题,因为memcpy可以拷贝任何类型的数据,所以它的参数是void *,但是我们知道void *的指针是不能直接解引用的,那我们怎么做才能让它一次访问一个空间呢?
当然是有办法的,我们可以把void *强制类型转换为char *的指针,而char *的指针每次解引用恰好能访问一个字节的内容。
好,梳理好思路,我们就来实现一下:

#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest+1;
		src = (char*)src + 1;
	}
	return ret;
}

注意:dest = (char*)dest+1; src = (char*)src + 1;
不要写成这样:((char*)dest)++;((char*)src)++;
因为这样的写法在某些编译器上可能通不过。

我们来看看效果:
在这里插入图片描述
没毛病。

1.3 思考

相信大家已经对memcpy了解的差不多了,那我们接下来再来思考一个问题:

我们刚才演示的是把一个数组的一些数据拷贝到另一个数组里面去了。是在两块不同的内存块进行操作的。
那我们能不能在同一个数组中,把前面的数据拷贝到后面的空间中呢?(也就是说,源空间和目标空间是有重叠的)
这样可以吗?我们可以来试一下:

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

像这样,把arr中的1,2,3,4,5放到4,5,6,7,8的位置。变成1,2,3,1,2,3,4,5,9,10。

我们来看看能不能实现:
在这里插入图片描述
我们发现不行,没有达到我们想要的结果:

为什么呢?
因为memcpy在实现的时候是从前向后拷贝的,如果我们想把 arr中的1,2,3,4,5放到4,5,6,7,8的位置。
当我们把1,2,3拷贝到3,4,5的位置之后,我们再去拷贝后面4,5的时候,会发现4,5已经被覆盖成1,2了。
在这里插入图片描述
所以这样是不行的。

那有没有什么好的解决办法呢?当然有,我们接着往下看。

2.移动内存块(可拷贝重叠内存块)——memmove

不知道大家有没有注意到,其实在上面对memcopy的介绍中就提到如何解决重叠内存块的拷贝问题了。
在这里插入图片描述

2.1 函数介绍

那我们就来了解一下memmove:
在这里插入图片描述

我们可以发现memcopy跟memmove的参数及返回类型其实是一样的,只不过memmove的功能更强大,可以实现重叠内存块的拷贝,或者说,它可以对内存块进行移动。
在这里插入图片描述

2.2 memmove的模拟实现

我们来继续讨论上面的那个问题:

在同一个数组中,把前面的数据拷贝(移动)到后面的空间中,或者把后面的数据拷贝(移动)到前面(即源空间和目标空间是有重叠的)。

还来看上面的例子:

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

把arr中的1,2,3,4,5放到4,5,6,7,8的位置。变成1,2,3,1,2,3,4,5,9,10。
我们已经测试过了,用我们模拟实现的my_memcpy是不行的,因为在从前向后拷贝的时候后覆盖掉4,5。
在这里插入图片描述

既然从前向后拷贝不行,那我们从后向前拷会不会就可以了呢?

试一下:
在这里插入图片描述

这样好像确实可以,那好了,那以后我们都从后向前拷贝就行了,是这样吗?
好像还是会出现问题

我们来看这种情景,还是这个数组:

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

如果我们相把5,6,7,8,9放到2,3,4,5,6的位置,从后向前拷贝能实现吗?
我们来试试看:
在这里插入图片描述
这样的话从前往后又不行了,当我们拷贝5,6是发现5,6已经被8,9覆盖了。
这种情况下,我们又需要从前往后拷了。
在这里插入图片描述

那这样的话,有时候需要从前向后,有时候需要从后向前,怎么搞呢?

其实对比上面两次出现的情况,我们可以发现:
注:数组随着元素下标的递增地址是从小到大的。
当源空间的起始地址dest小于目标空间的起始地址src时,我们需要从前向后拷贝。
当源空间的起始地址dest大于目标空间的起始地址src时,我们需要从后向前拷贝。

那我们就可以模拟实现memmove了。

在函数内部,我们只需判断一下,dest和src的大小就行了,然后决定从前向后还是从后向前拷贝。

void* my_memmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	void* ret = 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 ret;
}

我们来测试一下,还是上面的那个例子:
在这里插入图片描述

这次就达到效果了。

3. Visual Studio 对memcpy的实现

大家有没有注意到:

刚才在 1.3 测试把arr中的1,2,3,4,5放到4,5,6,7,8的位置。变成1,2,3,1,2,3,4,5,9,10。
我们用的是自己模拟实现的memcpy—— my_memcpy来测试的,当然它没有实现这种重叠内存块的拷贝。

那我们现在尝试用库函数memcpy自身来测试一下,看能不能实现这个重叠内存块的拷贝:
在这里插入图片描述

可以啊,我们发现memcpy也可以实现对重叠内存块的拷贝啊。那是不是我们模拟实现的没有达标呢?
其实不是的。

C语言标准规定的就是:

对于memcpy,只要能实现对不重叠的内存块的拷贝就行了;
但是对于memmove来说,需要它能够实现对重叠的内存块的拷贝;

但是

我们也看到了,Visual Studio对于memcpy功能的实现是比较强大的,达到了和memmove一样的标准
但我们不能指望所有的编译器提供的memcpy都能够实现对重叠内存块的拷贝。
这一点给大家说一下。

4. 内存比较函数—— memcmp

在这里插入图片描述

4.1函数介绍

memcmp的参数,其实和strncmp是非常相似的:
在这里插入图片描述
它们的区别在于strncmp只能比较字符类型的数据(因为函数参数设计的是char*
而对于memcmp来说,它不管内存中放的是什么类型的数据,都可以进行比较,因为memcmp的参数设计的是void*

除此之外,它们没什么不同,都是一个字节一个字节的进行比较,如果相同,继续比较下一个字节的内容,直至比较完num个字节的内容,返回值也一样:
在这里插入图片描述
我们练习一下memcmp的使用:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 1,2,3,7,9 };
	int ret = memcmp(arr1, arr2, 13);
	printf("%d", ret);
	return 0;
}

思考一下结果是什么?

比较arr1和arr2的前13个字节的内容:
我们可以画一个图,分析一下arr1和arr2所占空间中放的内容
首先我用的编译器(vs2022)上采用的是小端存储,即是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中
在这里插入图片描述

我们看看结果是不是-1呢?
在这里插入图片描述

4.2 memcmp的模拟实现

memcmp的实现思路其实也很简单,我们就一个字节一个字节的比较,如果相等就继续比较下一个字节,不相等就返回对于的值,如果比完了num个字节都相等,就返回0。
当然这里还是需要把void *强制类型转换为char *的指针,使得每次解引用恰好能访问一个字节的内容。

看代码:

int my_memcmp(const void* s1, const void* s2, size_t num)
{
	assert(s1 && s2);
	while (num--)
	{
		if (*(char*)s1 > *(char*)s2)
			return 1;
		else if(*(char*)s1 < *(char*)s2)
			return -1;
		else
			{
				s1 = (char*)s1 + 1;
				s2 = (char*)s2 + 1;
			}
	}
	return 0;
}

测试一下效果:
在这里插入图片描述

没问题,和memcmp一样的结果。

5. 内存设置函数—— memset

5.1函数介绍

memset 是用来干嘛的呢?

它可以把指定内存块的前 num 个字节设置为指定的值。
在这里插入图片描述
在这里插入图片描述

解释一下:

参数ptr 用来接收我们想要修改的内存块的地址,value接收我们想要设置的值,num用于指定想要设置的字节数,函数最终返回指针ptr 。

我们来练习一下memset 的使用:

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	memset(arr1, 0, 8);
	return 0;
}

把数组arr1的前8个字节内容设置为0。

看看效果:

在这里插入图片描述

当然value的值我们传字符也是可以的,只不过是以整型的形式传递而已。

当然字符的话是以其对应的ASCII码值作为设置的值
在这里插入图片描述

我们来试一下:

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	memset(arr1, 'a', 9);
	return 0;
}

将arr1的前9个字节设置为字符’a’.

看看结果:

在这里插入图片描述
每一个字节都变成了61,因为编译器给我们展示出来的是16进制,而字符’a’的ASCII码值为97,转换为16进制就是61,结果没问题。

5.2 memset的模拟实现

思路很简单,可以用一个while循环,循环num次,每次设置一个字节,直至把num个字节的内容设置成参数value的值,返回指向内存块的指针ptr。

上代码:

void* my_memset(void* ptr, int value, size_t num)
{
	assert(ptr);
	void* ret = ptr;
	while (num--)
	{
		*(char*)ptr = value;
		ptr = (char*)ptr + 1;
	}
	return ret;
}

看看效果:
在这里插入图片描述
可以达到与memset一样的效果。

好了,以上就是本篇文章的全部内容,欢迎大家指正!!!
在这里插入图片描述

相关文章:

  • 【DNS服务器的配置】实操
  • mysql索引下推与回表
  • 安装Scala
  • [C#小技巧]如何捕捉上升沿和下降沿
  • 一行代码,将2D转3D图表!
  • C++编程 杨辉三角详解
  • JavaScript 中的异步编程(上)
  • 【一起学数据结构与算法】快速教你了解并实现单链表
  • 用Pytorch实现一个线性回归
  • 【C++】二叉搜索树set/map
  • 最短路径查找Dijkstra算法
  • [数字媒体] Photoshop基础之图像校正、抠图(证件照)和融合
  • 【毕业设计】基于的单片机的移动硬盘设计与实现 - stm32 嵌入式 物联网
  • 使用Python的requests库发送SOAP请求,错误码415
  • Python爬虫技术系列-02HTML解析-lxml+BS4
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • Angular 响应式表单 基础例子
  • Brief introduction of how to 'Call, Apply and Bind'
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • es的写入过程
  • git 常用命令
  • go语言学习初探(一)
  • Java 多线程编程之:notify 和 wait 用法
  • SQLServer之索引简介
  • unity如何实现一个固定宽度的orthagraphic相机
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 让你的分享飞起来——极光推出社会化分享组件
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 算法-图和图算法
  • 探索 JS 中的模块化
  • 智能网联汽车信息安全
  • 最简单的无缝轮播
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 阿里云ACE认证之理解CDN技术
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • # Maven错误Error executing Maven
  • (10)ATF MMU转换表
  • (LeetCode C++)盛最多水的容器
  • (安卓)跳转应用市场APP详情页的方式
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (强烈推荐)移动端音视频从零到上手(下)
  • (实战篇)如何缓存数据
  • (四)Android布局类型(线性布局LinearLayout)
  • (学习日记)2024.01.09
  • (转)C#调用WebService 基础
  • (转)菜鸟学数据库(三)——存储过程
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .Net Winform开发笔记(一)
  • .NET上SQLite的连接
  • /dev下添加设备节点的方法步骤(通过device_create)