第21篇 C语言内存函数
2026/6/26 5:45:00 网站建设 项目流程

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

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如何逐字节修改数据。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询