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

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

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

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐