C语言常用的内存函数
在上一篇博客中我为大家分享了一些常用的字符串函数,以及它们的用法和模拟实现。通过字符串函数中的strcpy,我们能够做到将一个字符串中的内容拷贝到另一个字符串上,可如果有一天我们想把一个整型数组中的内容拷贝到另一个整型数组中呢?这样看来好像strcpy就不适用了,因为它只能对字符串进行拷贝。那么今天所提到的内存函数就能够实现整型数组,甚至其他类型的拷贝,让我们开始今天的学习吧~!
一、memcpy函数
我们可以看到,memcpy函数的返回类型以及参数类型大多都是void类型,这也就使得它能够接收不同类型的数据,并且能够对不同类型的数据加以处理~
①memcpy函数的使用
memcpy函数的作用是:将num字节的值从源指向的位置直接复制到目标指向的内存块。由于参数为void*类型,所以源指针和目标指针指向的对象的底层类型与此函数无关。(注:这个函数在遇到 '\0' 的时候并不会停下来。)
memcpy函数的第一个参数void* destination指向目标起始位置,代表想要改变的初始位置,第二个参数const void* source指向想要复制的数据的起始位置,它的作用是作为模板为目标复制字节,第三个参数size_t表示要复制的字节数。那么了解了memcpy函数的组成,让我们举个例子,尝试一下使用memcpy函数拷贝整型数组吧~
int main()
{int arr0[20] = { 0 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };memcpy(arr0, arr1, 10 * sizeof(int));for (int i = 0; i < 10; i++){printf("%d ", arr0[i]);}return 0;
}
甚至我们还能做到使用memcpy拷贝结构体
struct stu
{char name[20];int age;long id;
};
int main()
{struct stu s0 = { "xiaowang",20,10666 };struct stu s1 = { "xiaosong",40,10333 };memcpy(&s0, &s1, 8 * sizeof(char));memcpy(&s0, &s1, 7 * sizeof(int));printf("%s\n", s0.name);printf("%d\n", s0.age);printf("%ld\n", s0.id);return 0;
}

(我这里只是举一个例子,对于结构体的拷贝还需要学会结构体内存对齐,如果有机会的话下次我会单独用一篇博客来分享一下关于结构体内存对齐的知识)
②memcpy函数的模拟实现
不知道小伙伴们还记不记得我们之前学习过的qsort函数,让我们顺便回顾一下吧,qsort函数可以排序任意类型的数据,函数底层使用的是快速排序的方法。那么qsort函数是怎么做到能够排序任意类型数据的呢?没错,就是在不知道数据类型的情况下直接交换每一个字节。
而我们的memcpy函数也差不多是这个意思,我们最后传递的size_t类型参数就代表了需要替换的字节数,所以我们可以传递一个size_t类的num代表字节数,使用一个while循环,每循环一次,传递的num就减一,直到num为0时循环结束,此时正好交换了num个字节。
void* my_memcpy(void* arr0, const void* arr1, size_t num)
{assert(arr0 && arr1);while (num--){*(char*)arr0 = *(char*)arr1;arr0 = (char*)arr0 + 1;arr1 = (char*)arr1 + 1;}return (void*)arr1;
}
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前:\n");for (int i = 0; i < 5; i++){printf("%d ", arr0[i]);}my_memcpy(arr0, arr1, 5 * sizeof(int));printf("\n替换后:\n");for (int i = 0; i < 5; i++){printf("%d ", arr0[i]);}return 0;
}
那我们能不能尝试自己拷贝自己,做到将arr1里的4,5,6,7,8拷贝成它的前五位元素1,2,3,4,5呢?让我们尝试一下:
void* my_memcpy(void* arr0, const void* arr1, size_t num)
{assert(arr0 && arr1);while (num--){*(char*)arr0 = *(char*)arr1;arr0 = (char*)arr0 + 1;arr1 = (char*)arr1 + 1;}return (void*)arr1;
}
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}my_memcpy(arr1+3, arr1, 5 * sizeof(int));printf("\n替换后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}
可以看到在我们想用arr1前方的元素拷贝给后方时,却打印出了错误的答案,这是为什么呢?因为在对arr1进行拷贝的时候改变的是arr1本身,当第一次拷贝时arr1中的4变成了1,第二次拷贝时5变成了2,第三次拷贝时6变成了3,第四次拷贝时,我们需要将7变成4,可是此时arr1中的4已经被替换成了1,所以此时7变成了1,同理8也变成了2。这就说明了:(memcpy函数j仅仅是简单的从前往后进行拷贝,并没有考虑内存有重叠的情况,如果内存有重叠,其行为是不确定的。)
那么此时,下一个要介绍的函数就该登场了~
二、memmove函数
可以看到memmove的参数与memcpy函数是一致的,但与memcpy函数相较,memmove函数能够完美的处理重叠的情况。
①memmove函数的使用
• 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
• 如果源空间和目标空间出现重叠,就可以使用memmove函数处理。
那么我们使用memmove来证实上面的想法,就用刚刚memcpy打印出错的代码:
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}memmove(arr1+3, arr1, 5 * sizeof(int));printf("\n替换后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}
看来这次使用memmove确实解决了重叠的情况。
②memmove函数的模拟实现
想要实现memmove函数的模拟实现,就需要我们解决上面memcpy函数的重叠问题。那么memcpy函数的重叠问题是如何造成的呢?
刚刚的思路我们可以用这张图来表示,而我们按照memcpy的思路来进行拷贝就是从前往后拷贝,用图片来表示出来应该是这样的:
这就是出现了重叠的情况,我们想调用arr[3]时,其指向的元素已经从4变成了1,想要避免这种情况我们只需要在拷贝元素时保证需要拷贝的内容不被替换就好了。就比如此时,我们转换一下思路,我们现在想将1,2,3,4,5拷贝成4,5,6,7,8。这样的话能不能做到呢?是可以的!因为我们在将1拷贝成4后,拷贝的位置和需要拷贝的内容的位置都向后挪一位,此中重叠的元素为4和5,但4和5在最开始就为1和2拷贝成功啦,所以并不会发生错误的打印。这种情况就是拷贝内容初始位置在拷贝初始位置之后,此时需要使用顺序拷贝。
而对于上面将4,5,6,7,8变成1,2,3,4,5,我们可以尝试用逆序拷贝。
通过这个图可以知道,拷贝内容初始位置在拷贝初始位置之前,此时需要使用逆序拷贝。通过逆序拷贝的方式,就能够避免在拷贝前,拷贝内容被替换掉的情况。
想要模拟实现memmove函数,我们只需要将两种情况结合在一起,先判断两个初始位置谁在前谁在后,然后再对应的编写出解决方案就可以啦~(拷贝内容初始位置在拷贝初始位置之后时,直接使用memcpy的模拟函数就可以,拷贝内容初始位置在拷贝初始位置之前时,就逆序拷贝,从需要拷贝的最后一个字节往前拷贝就能够解决了)
void* my_memmove(void* arr0, const void* arr1, size_t num)
{assert(arr0 && arr1);if (arr0 < arr1){while (num--){*(char*)arr0 = *(char*)arr1;arr0 = (char*)arr0 + 1;arr1 = (char*)arr1 + 1;}}else{while (num--)*((char*)arr0 + num) = *((char*)arr1 + num);}return (void*)arr1;
}
int main()
{int arr0[20] = { 5,5,5,5,5 };int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("替换前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}my_memmove(arr1 + 3, arr1, 5 * sizeof(int));printf("\n替换后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}
这样就成功的模拟实现了memmove函数啦~
三、memset函数
memset的作用是:复制字符 value (注意,不是字符串,是一个字符)到参数 ptr 所指向的字符串的前 num 个字符。
memset和前两个函数最大的区别就是,前两个函数可以传输的是可以每一个的不同的,可以改变的,多样的。而memset能做到的只是将一个固定的字符拷贝到一段空间中。那么它的具体用法是什么呢?我们来尝试一下使用memset将一段数组进行清零。
int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };printf("清零前arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}memset(arr1, 0, 10 * sizeof(int));printf("\n清零后arr1:\n");for (int i = 0; i < 10; i++){printf("%d ", arr1[i]);}return 0;
}
能够看到这边也是成功清零了,那可能有人会问,那不是也可以把值全部变成1,全部变成2之类的吗?那让我们来尝试一下这个思路:
欸?怎么出现了这么一堆又奇怪又大的数字,这是怎么一回事呢?请仔细看刚刚的介绍,我们传输的并不是直接的一个值,而是一个将参数转化为二进制后填入的一个字节,比如此时我们想填入的是1,那么1转换为二进制形式就是0000 0001,但是arr中存放的int整形变量有四个字节,所以传进四个字节后其实这个对应的值会变成0000 0001 0000 0001 0000 0001 0000 0001。也就是一个非常大的数字。
由于之前传递的0对应的就是00 00 00 00所以会直接是0,并不是说明传递的是什么数字,就会是什么数字,想要理解memset函数,就要清楚这一点。而传递其他的值大部分比较麻烦,所以memset函数的主要作用还是使结构体或数组进行清零(注意并不是说就没有其他的作用了哦)。
四、memcmp函数

memcmp的作用是:比较从ptr1和ptr2指针指向的位置开始,向后的num个字节。
memcmp的用法与strncmp的用法很像,都是用于指定长度的比较大小,并且都是大于返回>0的数,小于返回<0的数,等于返回0。唯一不同的是memcmp函数是内存函数,通过它能够比较所有的类型,因为不论存储的内容是字符串,整数还是其它类型的数据,该函数都会逐个字节进行比较。
int main()
{int arr0[] = { 1,2,3,4,5,6 };int arr1[] = { 1,2,3,4,5,7 };if (memcmp(arr0, arr1, 6 * sizeof(int)) > 0){printf("arr0>arr1");}else if (memcmp(arr0, arr1, 6 * sizeof(int)) < 0){printf("arr0<arr1");}else{printf("arr0=arr1");}return 0;
}
由此就可以进行两个数组的大小的比较。同样字符串也是可以的:
int main()
{char arr0[] = "abcdef";char arr1[] = "abcdez";if (memcmp(arr0, arr1, 6 * sizeof(char)) > 0){printf("arr0>arr1");}else if (memcmp(arr0, arr1, 6 * sizeof(char)) < 0){printf("arr0<arr1");}else{printf("arr0=arr1");}return 0;
}
那么关于比较常用的内存函数的知识,就给大家分享到这里啦,如果有讲解的有问题或者不清楚的地方,还希望各位在评论区多多指出,我也会吸取教训,多多学习的,那么我们下期再见啦~
相关文章:
C语言常用的内存函数
在上一篇博客中我为大家分享了一些常用的字符串函数,以及它们的用法和模拟实现。通过字符串函数中的strcpy,我们能够做到将一个字符串中的内容拷贝到另一个字符串上,可如果有一天我们想把一个整型数组中的内容拷贝到另一个整型数组中呢&#…...
MP4 H.264 MPEG-4 MPEG-2
MP4 视频编解码技术 H.264 MPEG-4 MPEG-2 MP4 (MPEG-4 Part 14): Format: A digital multimedia container format.Use: Often used to store video, audio, subtitles, and still images.Compression: Can use different codecs, such as H.264, for video compression. H.264…...
nvm 切换、安装 Node.js 版本
nvm下载路径 往下拉找到Assets 下载后,找到nvm-setup.exe双击,一直无脑下一步,即可安装成功。 配置环境变量(我的是window11) 打开任务栏设置–搜环境变量 配置好后,点确定一层一层关闭 windowR 打开控制…...
基于区块链的合同存证应用开发
基于区块链的合同存证应用开发 任务一:环境准备 1.启动区块链网络 目录: /root/xuperchain/output/ 启动区块链网络 bash constrol.sh start2.创建钱包账户 目录: /root/xuperchain/output/ 创建tenant, landlord钱包账户,命令如下: bin/xchain-cli account newke…...
每日一题~ abc 365 E 异或运算(拆位+贡献)
处理位运算常用的方法: 拆位法(一位一位的处理,通常题目中会给出元素的最大是2的的多少次幂,当然也有给10的次幂的,自己注意一下就可以了) 常用的思想 : 算贡献。 异或的性质: A^A0 …...
前端八股文笔记【三】
JavaScript 基础题型 1.JS的基本数据类型有哪些 基本数据类型:String,Number,Boolean,Nndefined,NULL,Symbol,Bigint 引用数据类型:object NaN是一个数值类型,但不是…...
AI学习记录 - transformer的Embedding层
创作不易,免费的赞 前面有介绍了GPT2如何进行token化的过程,现在讲下transformer的Embedding层 Embedding层就是一个巨大的矩阵,边长分别是词汇表长度和词向量维度,矩阵里面的每一个数字都是一个随机初始化的,或者是…...
23-PCB封装名称的统一添加与管理
1.进入封装管理器 2. 选择对象,点击右侧添加按钮 3. 搜索所需要的封装 4.接受创建变更 5.执行变更 6.关闭...
【Python从入门到进阶】62、Pandas中DataFrame对象案例实践
接上篇《61、Pandas中DataFrame对象的操作(二)》 上一篇我们讲解DataFrame对象的统计分析、可视化以及数据导出与保存相关内容。本篇我们延续之前学习的DataFrame对象的知识,结合一个数据案例进行实践操作。 一、案例说明 我们将通过一个股…...
使用Python实现深度学习模型:智能环境监测与预警
介绍 智能环境监测与预警是保护生态环境和人类健康的重要手段。通过深度学习技术,我们可以实时获取环境数据,分析环境变化趋势,及时发出预警。本文将介绍如何使用Python和深度学习库TensorFlow与Keras来构建一个简单的环境监测与预警模型。 环境准备 首先,我们需要安装必…...
ThreadLocal的使用场景是什么
ThreadLocal 是 Java 中用于实现线程局部变量的工具,它提供了每个线程独立的变量副本,使得不同线程对该变量的操作不会相互干扰。以下是 ThreadLocal 的常见使用场景: 线程安全的对象共享: ThreadLocal 可以用来避免线程间共享状…...
【网络爬虫篇】逆向实战—某东:滑块验证码(逆向登录)2024.8.7最新发布,包干货,包详细
【网络爬虫篇】更多优秀文章借鉴: 1. 使用Selenium实现黑马头条滑块自动登录 2. 使用多线程采集爬取豆瓣top250电影榜 3. 使用Scrapy爬取去哪儿网游记数据 4. 数据采集技术综合项目实战1:国家水稻网数据采集与分析 5. 数据采集技术综合项目实战2&#x…...
为什么优质的酱香白酒都会带点苦味?
大家好,我是酱酒亮哥,不知大家有没有发现,在制作一杯美味的咖啡或是烘焙一块香脆的面包时,制作过程中都会有一些独特的味道和香气产生,对吧?同样地,酱香白酒的酿造过程也是一个复杂而精细的化学…...
软件测试常见面试题
软件测试阶段分为单元测试,集成测试,系统测试,验收测试。单元测试策略为对代码中的函数方法进行测试,目的是发现代码的问题。集成测试策略是模块中组合起来进行测试,要求发现与接口有关的问题。系统测试策略是子系统的…...
面试经典算法150题系列-接雨水
接雨水 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,…...
【C++】 类型转换深度探索:揭开类型转换的奥秘
🌈 个人主页:Zfox_ 🔥 系列专栏:C从入门到精通 目录 一: 🚀 C语言中的类型转换 二: 🔥 为什么C需要四种类型转换 三: 🔥 C强制类型转换 🥝 3.1 st…...
深入探索Webkit的Web Authentication API:安全与便捷的融合
Web Authentication API,通常被称为WebAuthn,是一个新兴的Web标准,旨在通过提供更安全、更便捷的认证方式来改善用户的在线体验。随着Webkit对WebAuthn的支持日益增强,本文将深入探讨这一API的功能、实现方式以及如何在Webkit浏览…...
Vue - 关于v-wave 波浪动画组件
Vue - 关于v-wave 波浪动画组件 这个动画库可以在标签中添加新的v-wave属性,来让点击标签元素后添加漂亮的波纹效果,并且可以根据父元素自动形成波纹的颜色,也可以自定义波纹颜色,持续时间,透明度,触发的对…...
计算机网络408考研 2019
计算机网络408考研2019年真题解析_哔哩哔哩_bilibili 2019 1 1 1 1...
实时捕捉与追溯:得物基于 eBPF 打造云上网络连接异常摄像头
近期我们容器 SRE 团队基于 eBPF 技术建设网络连接异常感知能力,灰度上线过程中发现了生产环境 10 以上的应用配置错误、程序 Bug 等问题。在和应用负责同学同步风险过程中,大家都挺好奇我们如何实现在对应用无侵入的情况下发现服务连接异常的。本篇文档…...
WSL2 Arch Linux快速安装Docker指南
1. 前言 在 WSL2 中使用 Arch Linux 时,Docker 是一个常用的工具。本文基于 Arch Linux Wiki 和相关技术资料,整理了完整的 Docker 安装流程,帮助读者快速完成配置。 2. 下载与启动设置 Arch Linux 的软件仓库已包含 Docker,可直接…...
郭老师-人生最顶级的活法:三句古训,一生受用
人生最顶级的活法 ——藏在《道德经》里的三句真言“老祖宗早就把答案写好了, 只是你一直忙着刷手机,没看见。”🌿 真正的自由, 不是拥有更多, 而是—— 需要更少,看清更多,止于恰到好处。&…...
Phi-3-mini-4k-instruct-gguf参数详解:重复惩罚penalty对技术文档生成影响
Phi-3-mini-4k-instruct-gguf参数详解:重复惩罚penalty对技术文档生成影响 1. 模型概述 Phi-3-mini-4k-instruct-gguf是微软Phi-3系列中的轻量级文本生成模型GGUF版本,特别适合问答、文本改写、摘要整理和简短创作等场景。这个开箱即用的中文文本生成模…...
家庭NAS搭建避坑指南:从硬件选购到TrueNAS配置的全流程心得
家庭NAS搭建实战:从零避坑到高效配置的全方位指南 为什么你需要一台家庭NAS? 记得三年前那次硬盘突然崩溃的经历吗?我丢失了孩子出生以来的所有照片和视频,那种懊悔至今难忘。正是这次教训让我意识到,家庭数据存储不能…...
仅限首批200名AI基础设施工程师:Cuvil 2024Q3内部编译诊断工具集(含AST可视化插件与算子融合热力图)
第一章:Cuvil编译器在Python AI推理中的定位与价值Cuvil编译器是一个面向AI推理场景的轻量级、Python原生友好的编译框架,专为优化动态图模型(如PyTorch TorchScript子集、ONNX子图及自定义算子图)在CPU/GPU边缘设备上的执行效率而…...
OpenClaw性能优化:降低Phi-3-mini-128k-instruct调用Token消耗的7个技巧
OpenClaw性能优化:降低Phi-3-mini-128k-instruct调用Token消耗的7个技巧 1. 为什么需要关注Token消耗? 当我第一次在本地部署OpenClaw并接入Phi-3-mini-128k-instruct模型时,就被它的长文本处理能力惊艳到了。但运行一周后查看账单…...
C# AI推理加速架构设计图(.NET 11专属GPU/CPU/NPU三模调度蓝图)
第一章:C# AI推理加速架构设计图总览C# AI推理加速架构以“跨层协同、软硬共生”为核心设计理念,构建从模型加载、计算调度到硬件执行的全栈优化通路。该架构并非简单封装原生推理引擎,而是通过抽象统一的IR(Intermediate Represe…...
RISC-V 基金会 Data Center SIG 第八次会议圆满结束,围绕AIOE和TG推进展开
一直以来,龙蜥社区在 RISC-V 生态建设中持续投入,并积极贡献上游社区。RISC-V International Data Center SIG 第八次会议内容见下: Atomic I/O Enqueue(AIOE )扩展提案 v4 提案评审 RISC-V International Data Cent…...
JointJS部署与打包终极指南:从开发到生产环境的完整实践
JointJS部署与打包终极指南:从开发到生产环境的完整实践 【免费下载链接】joint A proven SVG-based JavaScript diagramming library powering exceptional UIs 项目地址: https://gitcode.com/gh_mirrors/jo/joint JointJS作为一款基于SVG的JavaScript图表…...
别再当‘炼丹师’了!用SHAP值给你的PyTorch模型做个‘CT扫描’,一眼看懂特征在干嘛
用SHAP值透视PyTorch模型:从黑箱到透明决策的工程实践 当你的深度学习模型在测试集上表现优异,却在生产环境中频频失误时,是否曾怀疑过那些隐藏在权重矩阵背后的"暗箱操作"?传统模型评估指标就像体检报告上的数字&#…...
