第21篇 C语言内存函数
一、底层基础理论:内存函数核心概念
1.1 核心概念标准定义
1.1.1 基础概念梳理
C语言标准库提供了一系列用于直接操作内存的函数,它们与处理字符串的函数(如 strcpy)有本质区别。内存函数操作的对象是内存块,而非以 '\0' 结尾的字符串。
memcpy: 用于内存块的复制。它从源地址开始,向后复制指定数量的字节到目标地址。memmove: 同样用于内存块复制,但其核心特性是能够安全地处理源内存块与目标内存块重叠的情况。memset: 用于设置内存块的内容。它将指定内存区域的前若干个字节设置为给定的值。memcmp: 用于比较两个内存块的内容。它比较从两个指针开始的后若干个字节。
关键结论:内存函数以**字节(Byte)**为基本操作单位,不关心内存中存储的数据类型,也不会因为遇到 '\0' 而停止操作。
1.2 底层运行约束规则
1.2.1 硬件配套通识科普
从计算机硬件角度看,内存是由一系列可独立寻址的存储单元(字节)构成的线性空间。memcpy 等函数的工作方式,就是按照字节地址顺序,对这片空间进行读取和写入。这种设计使得它们非常高效和通用,可以复制任何类型的数据(整数、浮点数、结构体等),因为任何数据在内存中最终都表现为二进制位序列。
二、基础语法规范:memcpy 与 memmove
2.1 核心运算符语法定义
2.1.1 语法规则推导
memcpy 和 memmove 的函数原型相似,但行为约束不同。
-
void* memcpy(void* destination, const void* source, size_t num);- 功能:从
source指向的内存位置开始,向后复制num个字节的数据到destination指向的内存位置。 - 约束:如果
source和destination所指向的内存区域有任何重叠,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;
}
代码分析:
- 定义数组
arr1,内容为1到10。 - 调用
memmove(arr1 + 2, arr1, 20)。假设int为4字节,则复制20字节即5个整数。 - 源地址为
arr1(即&arr1[0]),目标地址为arr1 + 2(即&arr1[2])。 - 由于目标地址在源地址之后,且区域重叠,
memmove会从后向前复制,避免覆盖还未读取的源数据。 - 最终数组前7个元素变为
1, 2, 1, 2, 3, 4, 5,后续元素不变。
三、易混淆概念底层区分:memcpy 与 memmove 模拟实现
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;
}
代码分析:
if (dst <= src || (char*)dst >= ((char*)src + count))这个判断条件用于检测是否属于“安全”的复制情况。- 如果条件为真,说明两个内存块不重叠,或者目标块在源块的前面,此时从前往后复制是安全的。
- 如果条件为假,说明目标块在源块的后面且存在重叠,此时必须将
dst和src指针移动到各自区域的末尾,然后从后往前逐字节复制。
四、语法修饰与边界约束:memset 与 memcmp
4.1 修饰符分类与使用限制
memset 和 memcmp 是对内存块进行设置和比较的函数。
-
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);- 功能:比较
ptr1和ptr2指向的内存区域的前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;
}
代码分析:
int arr[5]在内存中占用5 * 4 = 20个字节(假设int为4字节)。memset(arr, 1, sizeof(arr))会将这20个字节的值全部设置为0x01。- 对于
arr[0],其内存中的4个字节变为0x01010101。 - 在小端系统上,这个十六进制数对应的十进制整数值是
16843009,而不是我们期望的1。 - 正确做法:若要初始化
int数组,应使用for循环逐个赋值。memset通常只用于将内存块清零(memset(arr, 0, sizeof(arr)))。
五、全章节逻辑闭环总结
- 内存函数基础:
memcpy、memmove、memset、memcmp是以字节为单位操作内存块的函数,不依赖'\0'结尾。 - 复制函数差异:
memcpy不处理内存重叠,行为未定义;memmove通过判断重叠方向,选择从前向后或从后向前复制,确保安全。 - 设置函数特性:
memset以字节为单位设置内存,在初始化非char类型数组时需谨慎,避免产生不符合预期的值。 - 比较函数规则:
memcmp按字节比较两个内存块,将字节视为unsigned char类型。
学习建议:
- 理解
memcpy和memmove的核心区别在于对内存重叠的处理。 - 牢记
memset的字节级操作特性,这是避免初始化错误的关键。 - 在调试时,可以观察内存窗口,直观地看到
memset如何逐字节修改数据。
木纹墙板映着暖光,屏幕里是未完成的代码与思考,桌角那抹亮粉,是疲惫时偷偷给自己的一点甜。
没有宏大的叙事,只有键盘敲击的节奏,和一杯凉透的咖啡——这大概就是当代打工人最真实的“仪式感”。
世界很大,但此刻,我的宇宙就在这方寸桌面之间
更多推荐


所有评论(0)