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

第21篇 C语言内存函数

一、底层基础理论:内存函数核心概念

1.1 核心概念标准定义
1.1.1 基础概念梳理

C语言标准库提供了一系列用于直接操作内存的函数,它们与处理字符串的函数(如strcpy)有本质区别。内存函数操作的对象是内存块,而非以'\0'结尾的字符串。

  • memcpy: 用于内存块的复制。它从源地址开始,向后复制指定数量的字节到目标地址。
  • memmove: 同样用于内存块复制,但其核心特性是能够安全地处理源内存块与目标内存块重叠的情况。
  • memset: 用于设置内存块的内容。它将指定内存区域的前若干个字节设置为给定的值。
  • memcmp: 用于比较两个内存块的内容。它比较从两个指针开始的后若干个字节。

关键结论:内存函数以**字节(Byte)**为基本操作单位,不关心内存中存储的数据类型,也不会因为遇到'\0'而停止操作。

1.2 底层运行约束规则
1.2.1 硬件配套通识科普

从计算机硬件角度看,内存是由一系列可独立寻址的存储单元(字节)构成的线性空间。memcpy等函数的工作方式,就是按照字节地址顺序,对这片空间进行读取和写入。这种设计使得它们非常高效和通用,可以复制任何类型的数据(整数、浮点数、结构体等),因为任何数据在内存中最终都表现为二进制位序列。

二、基础语法规范:memcpymemmove

2.1 核心运算符语法定义
2.1.1 语法规则推导

memcpymemmove的函数原型相似,但行为约束不同。

  • void* memcpy(void* destination, const void* source, size_t num);

    • 功能:从source指向的内存位置开始,向后复制num个字节的数据到destination指向的内存位置。
    • 约束:如果sourcedestination所指向的内存区域有任何重叠,memcpy的行为是未定义的。这意味着程序可能崩溃,也可能产生错误的数据。
  • void* memmove(void* destination, const void* source, size_t num);

    • 功能:与memcpy相同,复制num个字节。
    • 约束memmove能够正确处理源和目标内存区域重叠的情况。它通过判断重叠方向,选择从低地址向高地址复制,或从高地址向低地址复制,从而保证数据完整性。
2.2 变量定义与存储逻辑
2.2.1 内存存储逻辑

当数组等数据结构在内存中连续存储时,对其中一部分数据进行移动操作,就可能导致源和目标区域重叠。

验证代码
以下代码演示了memmove在处理重叠内存时的正确行为。

#include <stdio.h> #include <string.h> int main(void) { int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 将 arr1 的前 20 个字节(5个int)复制到 arr1+2 的位置 // 源区域: arr1[0] ~ arr1[4] // 目标区域: arr1[2] ~ arr1[6] // 两个区域存在重叠 memmove(arr1 + 2, arr1, 20); for (int i = 0; i < 10; i++) { printf("%d ", arr1[i]); } printf("\n"); return 0; }

代码分析

  1. 定义数组arr1,内容为110
  2. 调用memmove(arr1 + 2, arr1, 20)。假设int为4字节,则复制20字节即5个整数。
  3. 源地址为arr1(即&arr1[0]),目标地址为arr1 + 2(即&arr1[2])。
  4. 由于目标地址在源地址之后,且区域重叠,memmove会从后向前复制,避免覆盖还未读取的源数据。
  5. 最终数组前7个元素变为1, 2, 1, 2, 3, 4, 5,后续元素不变。

三、易混淆概念底层区分:memcpymemmove模拟实现

3.1 两类语法行为差异推导
3.1.1 内存行为对比分析

memcpy的模拟实现相对简单,只需从低地址向高地址逐字节复制。而memmove的实现必须考虑内存重叠的两种情况。

  • 非重叠或目标在源之前:可以安全地从低地址向高地址复制。
  • 目标在源之后且重叠:必须从高地址向低地址复制,以防止源数据在复制完成前被覆盖。
3.2 对照验证代码

以下代码展示了memmove的模拟实现,其中包含了处理重叠逻辑的核心判断。

#include <stdio.h> #include <assert.h> void* my_memmove(void* dst, const void* src, size_t count) { void* ret = dst; assert(dst && src); if (dst <= src || (char*)dst >= ((char*)src + count)) { // 情况1:非重叠,或目标地址在源地址之前(无重叠风险) // 从低地址向高地址复制 while (count--) { *(char*)dst = *(char*)src; dst = (char*)dst + 1; src = (char*)src + 1; } } else { // 情况2:重叠,且目标地址在源地址之后 // 必须从高地址向低地址复制 dst = (char*)dst + count - 1; src = (char*)src + count - 1; while (count--) { *(char*)dst = *(char*)src; dst = (char*)dst - 1; src = (char*)src - 1; } } return ret; }

代码分析

  1. if (dst <= src || (char*)dst >= ((char*)src + count))这个判断条件用于检测是否属于“安全”的复制情况。
  2. 如果条件为真,说明两个内存块不重叠,或者目标块在源块的前面,此时从前往后复制是安全的。
  3. 如果条件为假,说明目标块在源块的后面且存在重叠,此时必须将dstsrc指针移动到各自区域的末尾,然后从后往前逐字节复制。

四、语法修饰与边界约束:memsetmemcmp

4.1 修饰符分类与使用限制

memsetmemcmp是对内存块进行设置和比较的函数。

  • void* memset(void* ptr, int value, size_t num);

    • 功能:将ptr指向的内存区域的前num个字节设置为value
    • 核心约束:memset是以**字节(Byte)**为单位进行设置的,而不是以数组元素或数据类型为单位。参数value虽然为int类型,但实际只使用其低8位(一个字节)的值。
  • int memcmp(const void* ptr1, const void* ptr2, size_t num);

    • 功能:比较ptr1ptr2指向的内存区域的前num个字节。
    • 返回值
      • < 0:ptr1指向的内存块小于ptr2指向的内存块。
      • = 0: 两个内存块相等。
      • > 0:ptr1指向的内存块大于ptr2指向的内存块。
    • 比较规则:按字节进行比较,字节被当作unsigned char类型处理。
4.2 约束验证代码

以下代码演示了memset的字节级设置特性,这是初学者常见的误区。

#include <stdio.h> #include <string.h> int main(void) { int arr[5]; // 尝试将int数组的所有元素初始化为1 // 错误理解:认为会将每个int元素设为1 // 正确理解:会将数组占用的20个字节,每个字节都设为1 memset(arr, 1, sizeof(arr)); for (int i = 0; i < 5; i++) { printf("arr[%d] = %d\n", i, arr[i]); } return 0; }

代码分析

  1. int arr[5]在内存中占用5 * 4 = 20个字节(假设int为4字节)。
  2. memset(arr, 1, sizeof(arr))会将这20个字节的值全部设置为0x01
  3. 对于arr[0],其内存中的4个字节变为0x01010101
  4. 在小端系统上,这个十六进制数对应的十进制整数值是16843009,而不是我们期望的1
  5. 正确做法:若要初始化int数组,应使用for循环逐个赋值。memset通常只用于将内存块清零(memset(arr, 0, sizeof(arr)))。

五、全章节逻辑闭环总结

  1. 内存函数基础memcpymemmovememsetmemcmp是以字节为单位操作内存块的函数,不依赖'\0'结尾。
  2. 复制函数差异memcpy不处理内存重叠,行为未定义;memmove通过判断重叠方向,选择从前向后或从后向前复制,确保安全。
  3. 设置函数特性memset字节为单位设置内存,在初始化非char类型数组时需谨慎,避免产生不符合预期的值。
  4. 比较函数规则memcmp按字节比较两个内存块,将字节视为unsigned char类型。

学习建议

  • 理解memcpymemmove的核心区别在于对内存重叠的处理。
  • 牢记memset的字节级操作特性,这是避免初始化错误的关键。
  • 在调试时,可以观察内存窗口,直观地看到memset如何逐字节修改数据。

木纹墙板映着暖光,屏幕里是未完成的代码与思考,桌角那抹亮粉,是疲惫时偷偷给自己的一点甜。
没有宏大的叙事,只有键盘敲击的节奏,和一杯凉透的咖啡——这大概就是当代打工人最真实的“仪式感”。
世界很大,但此刻,我的宇宙就在这方寸桌面之间

http://www.jsqmd.com/news/1079565/

相关文章:

  • 2026年半导体指纹与光学指纹智能门锁实测对比:湿手、油污、假指模、老人浅纹场景数据报告
  • 混合与拉格朗日有限元耦合:精准求解应力集中的高效策略
  • 网站内容被收录但搜索流量极低?从技术角度聊聊搜索引擎内容解析机制的变化
  • 计算机毕业设计之jsp基于SSM的在线学习平台
  • 51-C16+时钟+校时+喂食+水位+加水喂水+三餐3定时+声光提醒+OLED屏+手动+自动+(无线方式选择)-3(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 彻底搞懂USART、UART、RS232、RS485、USB:嵌入式串口通信全家桶详解
  • 一文读懂大语言模型,普通人也能看懂的AI全景图
  • 四维流形对合Floer不变量:对称性、Seiberg-Witten理论与应用
  • 200 万 token 还是不够用?Codex 上下文浪费的根源和解法
  • 文件加密该选用什么软件,6 款适配多场景文件加密软件干货汇总
  • IDEA安装卡在“Configuring SDK”?(2024最新JDK 21+兼容性白皮书)
  • 2026山东咨询师CRM免费试用选型指南
  • Java毕业设计-基于 SpringBoot 的企业员工信息管理系统设计与实现 SpringBoot 框架下公司人事员工管理系统设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 一台设备联网,其实没有你想象得那么简单
  • 如何使用 OpenCode 模型免费运行 Claude 代码
  • VMware虚拟机安装Windows10系统
  • 网络安全零经验尝试技术手段破解邻居WIFI
  • AI工程化实战指南:从Newsletter到生产级LLM系统落地
  • ByteArrayInputStream和DataInputStream的源码分析和使用方法详细分析
  • 数据驱动PDF方法:从湍流条件平均估计到概率密度函数建模
  • 阿里Java面试核心讲(终极版):程序员面试必刷!
  • 外包区块链开发避坑指南!这8个坑千万别踩
  • 如何在5分钟内完成Honey Select 2的完整汉化与去码:终极技术配置指南
  • 11平台dota地图辅助免费16对战平台开图外挂下载dota全图辅助工具DOTA全图公益版
  • SGLang 与 TileLang 在 ROCm 生态中的适配现状
  • Cursor + Android Studio 插件完整方案
  • Lely CANopen configure 配置项与日志解读
  • 一文搞懂 Agent 的进化:从 RAG/ReAct 到 Skills/Harness/Loop,你的旧地图为什么不够用了
  • 放大50倍看4400机芯,这套日内瓦纹的加工公差才是底牌
  • 计算机Java毕设实战-面向中小企业的员工档案管理系统设计与实现 基于 SpringBoot 的员工考勤与人事管理系统设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】