在这里插入图片描述

🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言数据结构(C语言)EasyX游戏规划
✨ 从来绝巘须孤往,万里同尘即玉京

在这里插入图片描述

字符串专题收官之后,我们迎来C语言内存操作的核心内容!这一篇聚焦四大内存函数——memcpy(内存拷贝)、memmove(内存移动)、memset(内存设置)、memcmp(内存比较),详解它们的使用规则、核心差异和模拟实现逻辑,帮你吃透以字节为单位的内存操作底层原理!

前景回顾:字符串函数核心速记 📝

C 语言字符串入门:字符函数 + strlen 精讲(从使用到模拟实现)
C 语言字符串进阶:strcpy/strcat/strcmp 精讲
C 语言字符串高阶:strstr/strtok/strerror 精讲(含 strstr 模拟实现)

回顾字符串函数的核心特性,能帮我们更好理解内存函数的设计逻辑:

  1. 字符串函数依赖\0作为终止标志,仅能处理字符类型数据。
  2. 内存函数不关心数据类型,以字节为单位操作内存,通用性更强。
  3. 模拟实现库函数时,assert断言指针非空是保证代码健壮性的必备操作。

一、memcpy:不重叠内存的拷贝神器 📤

memcpy的全称是 memory copy,作用是从源内存地址拷贝指定字节数的数据到目标内存地址,和字符串函数strcpy不同,它不会因为遇到\0而停止

1. 函数核心要点(<string.h>)

  • 函数原型
    void* memcpy(void* destination, const void* source, size_t num);
    
    • destination:目标内存起始地址
    • source:源内存起始地址
    • num:需要拷贝的字节数
  • 核心特性
    ✅ 不关心内存中存储的数据类型,逐字节拷贝
    ✅ 遇到\0不会停止,严格按照num指定的字节数拷贝
    仅适用于不重叠的两块内存区域

2. 实战示例:拷贝整型数组

#include <stdio.h>
#include <string.h>
int main()
{
    int arr1[] = {1,2,3,4,5}; // 占 5*4=20 字节
    int arr2[10] = {0};
    // 拷贝arr1的20个字节到arr2
    memcpy(arr2, arr1, 20);
    // 打印arr2:1 2 3 4 5 0 0 0 0 0
    for(int i=0; i<10; i++) printf("%d ", arr2[i]);
    return 0;
}

3. 模拟实现:逐字节拷贝

memcpy的模拟实现核心是强转为char*类型,因为char*每次操作正好是1个字节,完美匹配内存函数的设计逻辑。

#include <stdio.h>
#include <assert.h>

void* my_memcpy(void* dest, const void* src, size_t num)
{
    assert(dest && src); // 断言指针非空
    void* ret = dest;    // 保存目标起始地址,用于返回
    
    // 逐字节拷贝
    while (num--)
    {
        *(char*)dest = *(char*)src;
        src = (char*)src + 1;
        dest = (char*)dest + 1;
    }
    
    return ret;
}

// 测试代码
int main()
{
    int arr1[] = {1,2,3,4,5,6,7,8,9,10};
    int arr2[10] = {0};
    my_memcpy(arr2, arr1, 20); // 拷贝前5个整型元素
    for(int i=0; i<10; i++) printf("%d ", arr2[i]);
    return 0;
}

4. 注意事项:重叠内存的坑 ❗

如果用my_memcpy处理重叠内存,会出现数据覆盖问题:

// 错误案例:拷贝重叠区域
int arr[] = {1,2,3,4,5,6,7,8,9,10};
// 想把 {3,4,5,6,7} 拷贝到 {1,2,3,4,5} 的位置
my_memcpy(arr, arr+2, 20);
// 实际输出:{3,4,3,4,3,6,7,8,9,10} → 数据被覆盖

原因是my_memcpy从前往后拷贝,先拷贝的内容会覆盖后面待拷贝的数据。

💡 补充:VS2022等编译器对memcpy做了优化,能处理重叠内存,但标准C规定memcpy只负责不重叠内存,跨平台开发时要严格遵守标准,重叠内存请用memmove

二、memmove:处理重叠内存的升级版 🔄

memmove的全称是 memory move,它是memcpy的增强版,支持重叠内存区域的拷贝,是实际开发中更安全的选择。

1. 函数核心要点(<string.h>)

  • 函数原型
    void* memmove(void* destination, const void* source, size_t num);
    
    参数含义和memcpy完全一致。
  • 核心优势
    ✅ 兼容重叠内存和不重叠内存的拷贝
    ✅ 底层通过判断拷贝方向(从前往后/从后往前)避免数据覆盖

2. 核心思路:分情况选择拷贝方向

我们以数组 {1,2,3,4,5,6,7,8,9,10} 为例,分析destsrc的位置关系,确定拷贝方向:

内存位置关系 拷贝方向 原因
dest <= src(目标在源左边) 从前往后 先拷贝的内容不会覆盖待拷贝数据
dest > src(目标在源右边) 从后往前 避免先拷贝的内容覆盖后面的数据

3. 模拟实现:双向拷贝逻辑

#include <stdio.h>
#include <assert.h>

void* my_memmove(void* dest, const void* src, size_t num)
{
    assert(dest && src);
    void* ret = dest;
    
    if (dest <= src) // 情况1:目标在源左边 → 从前往后拷贝
    {
        while (num--)
        {
            *(char*)dest = *(char*)src;
            src = (char*)src + 1;
            dest = (char*)dest + 1;
        }
    }
    else // 情况2:目标在源右边 → 从后往前拷贝
    {
        while (num--)
        {
            // 从最后一个字节开始拷贝
            *((char*)dest + num) = *((char*)src + num);
        }
    }
    
    return ret;
}

// 测试重叠内存拷贝
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    // 把 {3,4,5,6,7} 拷贝到 {1,2,3,4,5} 的位置
    my_memmove(arr, arr+2, 20);
    // 正确输出:3 4 5 6 7 6 7 8 9 10
    for(int i=0; i<10; i++) printf("%d ", arr[i]);
    return 0;
}

三、memset:按字节设置内存值 🎨

memset的全称是 memory set,作用是以字节为单位,将指定内存区域的每个字节都设置为目标值。它是初始化内存的常用工具,但极易因使用不当踩坑。

1. 函数核心要点(<string.h>)

  • 函数原型
    void* memset(void* ptr, int value, size_t num);
    
    • ptr:要设置的内存起始地址
    • value:要设置的字节值(虽然参数是int,但实际只取低8位)
    • num:要设置的字节数
  • 核心特性
    ✅ 逐字节设置值,不是逐元素
    ✅ 常用于初始化字符数组,或把内存置为0

2. 正确用法:设置字符数组

#include <stdio.h>
#include <string.h>
int main()
{
    char arr[] = "hello world";
    // 从第3个字符开始,连续设置5个字节为 'x'
    memset(arr+2, 'x', 5);
    printf("%s\n", arr); // 输出:hexxxxxld
    return 0;
}

3. 经典踩坑:设置非字符类型数组 ❌

很多人会用memset初始化整型数组,结果和预期不符:

#include <stdio.h>
#include <string.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    // 想把数组每个元素设为1 → 实际是每个字节设为1
    memset(arr, 1, 40); // 数组占 10*4=40 字节
    return 0;
}

内存监视结果:每个整型元素的4个字节都被设为0x01,因此每个元素的值是 0x01010101(十进制 16843009),完全不是预期的1!

💡 结论:memset仅适合设置单字节数据(如char),或把内存置为0(value=0时,任何类型元素都是0)。

四、memcmp:按字节比较内存区域 🆚

memcmp的全称是 memory compare,作用是以字节为单位,比较两块内存区域的前num个字节。它和strcmp的区别是:不依赖\0,可比较任意类型数据。

1. 函数核心要点(<string.h>)

  • 函数原型
    int memcmp(const void* ptr1, const void* ptr2, size_t num);
    
    • ptr1/ptr2:要比较的两块内存起始地址
    • num:要比较的字节数
  • 返回值规则
    ▶ 若 ptr1 > ptr2 → 返回 大于0 的值
    ▶ 若 ptr1 == ptr2 → 返回 0
    ▶ 若 ptr1 < ptr2 → 返回 小于0 的值

2. 实战示例:比较整型数组

#include <stdio.h>
#include <string.h>
int main()
{
    int arr1[] = {1,2,3,4,5}; // 内存布局:01 00 00 00  02 00 00 00 ...
    int arr2[] = {1,2,3,4,0x12333301}; // 第5个元素内存:01 33 33 12
    // 比较前17个字节(前4个元素+第5个元素的第1个字节)
    int r = memcmp(arr1, arr2, 17);
    printf("%d\n", r); // 输出:正数(arr1第17字节是05,arr2是01)
    return 0;
}

写在最后 📝

内存操作函数是C语言直接操控内存的核心工具,和字符串函数相比,它们的通用性更强,适用范围更广,但也更容易因忽视字节操作的特性而踩坑。

掌握这些函数的关键在于三点:

  1. 区分memcpymemmove的使用场景(重叠/不重叠内存);
  2. 牢记memset逐字节设置,而非逐元素;
  3. 理解memcmp的比较规则是基于字节的ASCII码值。

这些函数的模拟实现逻辑,也是笔面试中考察指针和内存操作的高频考点,建议大家手动敲一遍代码,加深对底层原理的理解。至此,C语言字符串和内存操作的核心内容就全部讲解完毕啦!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐