【C语言】内存函数的使用和模拟实现
文章目录
- 一、memcpy的使用和模拟实现
- 二、memmove的使用和模拟实现
- 三、memset的使用
- 四、memcmp的使用
一、memcpy的使用和模拟实现
在之前我们学习了使用和模拟实现strncpy函数,它是一个字符串函数,用来按照给定的字节个数来拷贝字符串,那么问题来了我们想拷贝的不是字符串,而是整型、浮点型的数据,该怎么办呢?
这时候就要使用我们的内存函数memcpy,mem是memory的缩写,它原本是记忆的意思,在这里是内存的意思,它的作用范围就宽泛多了,因为它是对内存块的内容进行拷贝,不管内存中存放的是什么数据类型,都可以通过拷贝内存块来实现拷贝
但是使用函数memcpy需要包含的头文件还是<string.h>,接下来我们来看看这个函数的原型:
void * memcpy ( void * destination, const void * source, size_t num );
它有三个参数,可以看到,它的前两个参数不再是char * 指针,而是void*的指针了,因为我们不再知道要拷贝的内容具体是什么数据类型,所以可以使用void * 的指针,而它的第三个参数是一个无符号整型,代表了要拷贝的内存的字节数
它的返回类型是void * ,也是由于不知道需要操作的数据类型是什么,所以使用void * ,那它具体返回的地址是什么呢?我们可以参照字符串函数strcpy,它返回的就是目标空间的首地址,memcpy函数也是这样,返回目标空间的首地址,也就是这里的destination
接着我们可以总结出memcpy函数的特点:
- 函数memcpy从source的位置,也就是源空间的首地址开始,向后复制num个字节的数据到destination指向的内存位置
- 这个函数在遇到 ‘\0’ 的时候并不会停下来,它拷贝多少数据完全看第三个参数
- 如果source和destination有任何的重叠,复制的结果都是未定义的
接下来我们来简单使用一下这个函数,用它来拷贝一个整型数组,如下:
#include <stdio.h>
#include <string.h>int main()
{int arr1[20] = { 0 };int arr2[] = { 1,2,3,4,5,6,7,8,9,10 }; memcpy(arr1, arr2, sizeof(arr2));for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}
我们来看看运行结果:
可以看到它已经帮我们把数据完全拷贝过来了,它是怎么做到的呢?我们来试着模拟实现一下这个函数,就会发现其实并不难,它会结合我们学过的qsort实现和strcpy实现的知识,现在我们赶紧来实现一下吧!
- 函数命名:my_memcpy
- 函数参数:照抄memcpy的参数,简化一些长的名字:
void* my_memcpy ( void* dest, const void* src, size_t num )
- 函数实现:
(1)老规矩,首先进行一次断言,确保这两个指针不是空指针
(2)由于要返回目标空间的首地址,所以要创建一个void*的指针变量start来存储,用于最后的返回
(3)这里由于不知道是什么类型的数据,所以我们不能妄自定义一个数据类型,这里我们可以采用qsort里面的思想,将它们转为字符指针,一个字节一个字节的拷贝,这样就可以确保能够完美拷贝所有数据
(4)所以我们创建一个while循环,每进行一次循环就让num–,每一次循环我们就进行一个字节的拷贝,并且拷贝完后让dest和src往后走一个字节
(5)进行一个字节的拷贝就很简单了,只需要将dest和src强制类型转换为字符指针就可以了,主要是让它们往后面走一个字节不能使用(char * )dest++,所以我们这里可以采用++最原始的操作,就是给它+1,然后赋值给dest,如下:
dest = (char*)dest + 1;
(6)最后一步就是返回之前存下的变量start
- 函数代码:
#include <assert.h>void* my_memcpy(void* dest, const void* src, size_t num)
{assert(dest && src);void* start = dest;while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}return start;
}
- 函数测试:
最后我们来探讨一个问题,memcpy能否自己对自己进行拷贝呢?比如有一个数组arr,存放的是1到10的数字,能不能将从1开始的4个整型数据,拷贝到从5开始的4个整型数据,如下图:
能否将绿色圆圈内的数据作为源数据,拷贝到蓝色圆圈的空间中,使得数组中的数据变成1 2 3 4 1 2 3 4 9 10,问题就在于怎么找到从5开始的地址
arr是首元素地址,所以很容易想到,可以使用arr+4来作为目标空间地址,而arr作为源地址,我们来测试一下,如图:
可以看到,它成功实现了,接着我们继续思考,如果在拷贝时源空间和目标空间有重叠呢?如图:
这个时候我们想要经过拷贝后,数据变成1 2 3 4 3 4 5 6 9 10,memcpy能否帮我们实现拷贝呢?如图:
可以看到居然失败了,这是为什么呢?我们可以画图求解:
到经过第三个整型的拷贝时我们发现了问题,原本该被拷贝的5现在已经变成了3,所以在7,的位置放的是3,在8的位置放的是4,所以最后整个数组变成了这个样子:
跟我们打印出来的样子一模一样,那么怎么才能实现怎么内存重叠的拷贝呢?这个就要用到我们马上要学习的memmove函数了
但是在学习memmove函数之前,我们先插个题外话,刚刚我们一直使用的是自己实现的memcpy,无法处理内存重叠的情况,那库里面的那个memcpy函数本尊呢?能否实现,我们来看看:
可以看到,神奇的事情发生了,库里面的memcpy居然可以处理这种内存重叠的情况,那是不是我们写的memcpy太挫了呢?
很明显不是,是因为C语言规定了memcpy只处理没有内存重叠的情况,有内存重叠的情况交给memmove函数解决,这里的memcpy函数又为什么能够解决这个问题呢?
这个就涉及到编译器的问题了,比如C语言规定memcpy只处理没有内存重叠的情况,而VS的memcpy在处理了没有内存重叠的基础上,还实现了有内存重叠的情况,相当于老师只要求你考60分就能及格,就能到达要求,而你考了100分
所以不用担心是不是我们的momcpy函数实现的有问题,我们实现的momcpy已经满足C语言的规定了,已经合格了,没有问题
二、memmove的使用和模拟实现
memmove函数相当于时memcpy函数的进阶版,它不仅可以实现C语言规定的memcpy函数的功能,处理没有内存重叠的情况,还能处理存在内存重叠的情况,使用它也需要包含头文件string.h
我们来看看memmove的原型:
void * memmove ( void * destination, const void * source, size_t num );
可以看到和memcpy的原型长得一模一样,它们参数的含义和返回值都相同,这样就不再赘述
我们可以总结一下它们的不同:
- 和memcpy的差别就是memmove函数处理的源内存块和⽬标内存块是可以重叠的
- 如果源空间和⽬标空间出现重叠,就得使⽤memmove函数处理
接着我们首先来测试一下memmove能否实现memcpy的功能,如下图:
很明显我们看到memmove成功实现了,接着我们继续用memmove测试,让它替我们处理内存重叠的情况,如下:
可以看到memmove完美替我们解决了问题,我们接下来就来学习它的模拟实现:
- 函数命名:my_memmove
- 函数参数:
void* my_memmove ( void* dest, const void* src, size_t num )
- 函数实现:
(1)老规矩,对dest和src断言,确保它们不是空指针
(2)然后创建一个变量start用来存储dest的值,用于最后的返回
(3)我们来想想怎么解决内存重叠的情况,根据之前的尝试,我们知道,根据memcpy那样实现肯定不行,而memcpy实现时我们采用的是从前往后拷贝,我们可以来尝试一下从后往前拷贝
(4)我们可以画一个图试试,如下:
很明显看到居然成功了,说明这种情况从后往前进行拷贝是可以的,那么是不是所有情况都可以这样呢?我们接着继续讨论
(5)我们将上面的源空间和目标空间交换试一试:
这里就发现问题了,再想从后往前拷贝一个整型时,我们发现5和6都已经被覆盖了,无法得到了正确结果,所以光从后往前拷贝是行不通的
(6)经过简单的思考,我们可以发现在上图的情况下,从前往后进行拷贝居然又可以了,问题就在于我们如何判断什么时候从前往后拷贝,什么时候从后往前拷贝
(7)我们可以根据dest和src的位置判断,当目标空间首地址dest在源空间首地址src前时,就是分析的第(5)点中,我们从前往后拷贝,当目标空间首地址dest在源空间首地址src后面时,也就是分析第(4)点中,我们从后往前拷贝
(8)我们之前说过,数组的空间是连续的,并且随着下标的增大,地址也是逐渐增大的,所以我们可以发现当dest>src,就正常从前往后拷贝,当dest<src时,就从后往前拷贝
(9)从前往后拷贝我们之前在memcpy讲过,就不再赘述了,如下:
if (dest < src){while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}
(10)主要是要解决从后往后拷贝的问题,关键就在于找到dest和src空间的末尾地址,方法也很巧妙,我们可以根据while(num–),当第一次进入循环num就已经-1了,这时我们让dest和src加上num,就可以得到dest和src空间的末尾地址,这时就把src指向的内容赋值给dest指向的内容,然后随着下一次num–,dest和src加上num就跟着改变了,依次类推就可以实现从后往前拷贝,如下:
else{while (num--){*((char*)dest + num) = *((char*)src + num);}
- 函数代码:
#include <assert.h>void* my_memmove(void* dest, const void* src, size_t num)
{assert(dest && src);void* start = 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 start;
}
- 函数测试:
可以看到确实实现memmove的功能了
三、memset的使用
顾名思义,memset就是设置内存,它的作用就是一次性将num个字节全部置为某个值,使用它需要包含头文件string.h,我们来看看它的原型:
void * memset ( void * ptr, int value, size_t num );
它的第一个参数就是要设置的数组的首元素地址,第二个参数是要设置的值,第三个参数就是要设置多少个字节
我们要注意的是第二个参数是int类型的,所以一般不会用在浮点型当中,可以用在整型和字符型,因为字符型本质上存储的是ascll码值,也相当于整型
所以当数组是整型数组和字符数组时,可以通过这个函数来设置它们的值
我们首先测试一下整型数组,将所有数组的元素设置为0,如图:
使用起来是不是特别方便呢?一般会用在竞赛或者项目中,需要多组输入之类的,使用完一个数组,需要把它的元素都置为0
接下来我们想想,能不能使用这个函数将数组中的所有元素更改为1,如图:
可以看到失败了,这是为什么呢?这是因为memset设置的单位是字节,而整型有4个字节,每一个字节都设置为1,这个数就很大了,我们来看看内存窗口,如图:
接下来我们再来测试将字符数组全部弄成字符’x’,如图:
可以看到,memset连带着\0和空格都改成了字符x,当然,如果不想\0被改掉,在写最后一个参数时可以-1
到这里我们就讲完了memset,至于它的模拟实现,可以自行去实现,因为比较简单,只需要一个字节一个字节将对应的内容改成给出的数据即可,这里就不再赘述
四、memcmp的使用
它跟我们学习过的strncmp有点像,strncmp可以根据给出的字节数来比较字符串的大小,而memcmp是根据给出的字节数来比较各种类型的数据的大小,使用它需要包含头文件string.h,接下来我们来看看它的原型:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
可以看到它和strncmp的参数一模一样,第一个参数是要比较的内容的首地址,第二个也是如此,第三个参数用来指定要比较的字节的个数,而返回值也和strncmp的规则一样,前一个大就返回大于0的数,后一个大返回小于0的数,相等则返回0
接下来我们就用它来比较一下整型数组,如下:
#include <stdio.h>
#include <string.h>int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };int arr2[] = { 1,2,3,3 };int ret = memcmp(arr1, arr2, 4 * sizeof(int));printf("经过4个整型的比较:");if (ret > 0)printf("arr1更大\n");else if (ret < 0)printf("arr2更大\n");elseprintf("arr1和arr2相等\n");return 0;
}
运行结果:
在比较时,还需要注意一点,就是设置的要比较的字节数要有意义,比如arr2现在只有4个整型数据,如果要比较5个整型数据,也就是20个字节,就会读取到无效数据,所以最好保证字节数有意义
那么它能否比较字符串呢?我们可以来测试一下:
可以看到memcmp也可以比较字符串,至于memcmp的模拟实现可以自行完成,也是一个一个字节去比较,这里就不再赘述了
我们的内存函数讲解就到这里结束了,如果有什么不懂的,欢迎在评论区提问
相关文章:

【C语言】内存函数的使用和模拟实现
文章目录 一、memcpy的使用和模拟实现二、memmove的使用和模拟实现三、memset的使用四、memcmp的使用 一、memcpy的使用和模拟实现 在之前我们学习了使用和模拟实现strncpy函数,它是一个字符串函数,用来按照给定的字节个数来拷贝字符串,那么问…...

在WPF中实现多语言切换的四种方式
在WPF中有多种方式可以实现多语言,这里提供几种常用的方式。 一、使用XML实现多语言切换 使用XML实现多语言的思路就是使用XML作为绑定的数据源。主要用到XmlDataProvider类. 使用XmlDataProvider.Source属性指定XML文件的路径或通过XmlDataProvider.Document指定…...
30min 的OpenCV learning Note
1.安装python和pycharm与环境搭配 打开Windows终端:(winR)(一般使用清华镜像网站安装库比较快) pip install opencv-contrib-python -i https://pypi.mirrors.ustc.edu.cn/simple 或者 python -m pip install open…...

C--编译和链接见解
欢迎各位看官!如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论 感谢各位看官的支持!!! 一:翻译环境和运行环境 在ANSIIC的任何一种实现中,存在两个不同的环境1,…...
【QT Quick】基础语法:基础类与控件
QML 的基础类和控件中,我们可以看到主要的几个分类:基础控件类、窗口类以及组件类。以下是对这些控件及其属性、继承关系等的详细讲解: 控件关系总结 QtObject 是所有 QML 对象的基类。它定义了基础属性,主要用于逻辑和数据封装…...

使用 SSH 连接 Docker 服务器:IntelliJ IDEA 高效配置与操作指南
使用 SSH 连接 Docker 服务器:IntelliJ IDEA 高效配置与操作指南 本文详细介绍了如何在 2375 端口未开放的情况下,通过 SSH 连接 Docker 服务器并在 Idea 中进行开发。通过修改用户权限、生成密钥对以及配置 SSH 访问,用户可以安全地远程操作…...
Gas费用是什么?
Gas费用是什么? 每5个Byte 需要1个GasGasLimit 用来限制合约最多执行多少次运算GasPrice 每次计算需要支付的费用在Web3的语境中,尤其是在以太坊(Ethereum)这样的区块链平台上,Gas费是一个核心概念。以下是关于Gas费的详细解释: 1. 定义 Gas是以太坊网络上的计算单位,…...
大语言模型(LLM)的子模块拆拆分进行联邦学习;大语言模型按照多头(Multi-Head)拆分进行联邦学习
目录 大语言模型(LLM)的子模块拆拆分进行联邦学习 方式概述 简单示例 大语言模型按照多头(Multi-Head)拆分进行联邦学习 场景设定 多头拆分与联邦学习 示例说明 大语言模型(LLM)的子模块拆拆分进行联邦学习 大语言模型(LLM)的子模块拆分进行联邦学习,主要涉及…...

Qt 概述
1. Qlabel HelloWorld 程序 使用纯代码实现 // widget.cpp Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 给当前这个lable对象,指定一个父对象QLabel* label new QLabel(this);// C语言风格的字符串可以直接…...

移动应用的界面配置-手机银行APP
设置登录界面为线性布局,组件垂直居中排列设置主页为滚动模式,包括布局、添加背景图片设置按钮样式,包括形状、边框线的宽度和颜色 设置登录界面 设置界面为线性布局,组件垂直居中排列 --android:gravity"center_vertical…...

微服务nginx解析部署使用全流程
目录 1、nginx介绍 1、简介 2、反向代理 3、负载均衡 2、安装nginx 1、下载nginx 2、解压nginx安装包 3、安装nginx编辑 1、执行configure命令 2、执行make命令 4、启动nginx 1、查找nginx位置并启动 2、常用命令 3、反向代理 1、介绍反向代理配置 1、基础配置…...

华硕天选笔记本外接音箱没有声音
系列文章目录 文章目录 系列文章目录一.前言二.解决方法第一种方法第二种方法 一.前言 华硕天选笔记本外接音箱没有声音,在插上外接音箱时,系统会自动弹出下图窗口 二.解决方法 第一种方法 在我的电脑上选择 Headphone Speaker Out Headset 这三个选项…...
Unity中Socket_TCP异步连接,加入断线检测以及重连功能
1、服务端 using System; using System.Collections.Generic; using System.Text; #region 命名空间 using System.Net; using System.Net.Sockets; using System.Threading; using UnityEngine; #endregionnamespace AsynServerConsole {/// <summary>/// Tcp协议异步通…...
Android build子系统(01)Ninja构建系统解读
说明:本文将解读Ninja构建系统,这是当前Android Framework中广泛使用的构建工具。我们将从Ninja的起源和背景信息开始,逐步解读Ninja的优势和核心原理,并探讨其一般使用场景。然后介绍其在Android Framework中的应用及相关工具&am…...
徐老师的吉祥数
题目背景 文件读写 输入文件avoid.in 输出文件avoid.out 限制 1000ms 512MB 题目描述 众所周知, 3这个数字在有些时候不是很吉利,因为它谐音为 “散” 所以徐老师认为只要是 3的整数次幂的数字就不吉利 现在徐老师想知道,在某个范围[l,r] …...

使用html写一个能发起请求的登录界面
目录 head部分 内联样式部分 body部分 login-form类的div myModal类的div id script部分 总的代码 界面与操作演示 <!DOCTYPE html> <html lang"en"> <!DOCTYPE html> 这是文档类型声明,告诉浏览器这是一个 HTML文档。 <…...

五子棋双人对战项目(2)——登录模块
目录 一、数据库模块 1、创建数据库 2、使用MyBatis连接并操作数据库 编写后端数据库代码 二、约定前后端交互接口 三、后端代码编写 文件路径如下: UserAPI: UserMapper: 四、前端代码 登录页面 login.html: 注册页面…...
几种操作系统和几种cpu
常见的操作系统:windows,linux,macOS,统信,deepin,raspberry,andriod,iOS,鸿蒙,等等。 常见的cpu:intel,amd,龙芯&#x…...

[Cocoa]_[初级]_[使用NSNotificationCenter作为目标观察者实现时需要注意的事项]
场景 在开发Cocoa程序时,由于界面是用Objective-C写的。无法使用C的目标观察者[1]类。如果是使用第二种方案2[2],那么也需要增加一个代理类。那么有没有更省事的办法? 说明 开发界面的时候,经常是需要在子界面里传递数据给主界面࿰…...

彩虹易支付最新版源码及安装教程(修复BUG+新增加订单投诉功能)
该源码当前版本为较新的版本,新增了订单投诉功能和一套精美的二次元模板。 此版本为全开源版本,所有文件均未加密。系统默认安装完成后无法直接打开,需要进一步配置。 本站特别针对BUG文件进行了修复,且在PHP7.4环境下表现良好。…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...