C语言学习:内存函数

上文我们讲了字符串函数strcpy 这个函数的局限性太大 只能用来拷贝字符串

今天我们学习的内存函数 就可以实现任意类型的拷贝****大大提高了实用性

这有点类型于qsort函数 针对任意类型

1.memcpy

一、memcpy 核心知识点

1. 函数原型

复制代码
void * memcpy ( void * destination, const void * source, size_t num );

2. 功能说明

  • 从**source** 指向的内存位置 ,向后复制 num 个字节 的数据,到**destination 指向的内存位置**。
  • 不关心内存中存放的数据类型void* 万能指针),只按字节拷贝
  • ⚠️ 关键限制 :如果 sourcedestination内存有重叠结果是未定义的 (重叠场景要用 memmove)。
  • 这里的重叠指的是:对同一个内存空间进行拷贝整型数组 举例就是 数组内 拷贝到同一个数组内
  • 头文件#include <string.h>

3. 参数与返回值

参数 含义
destination 目标内存地址(拷贝的数据存这里)
source 源内存地址(要拷贝的数据从这里来)
num 要拷贝的字节数注意:不是元素个数!
  • 返回值:拷贝完成后,返回目标空间的起始地址(即 destination)。

二、代码演示解读

复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    int arr1[] = {1,2,3,4,5,6,7,8,9,10};
    int arr2[10] = { 0 };
    
    // 拷贝 20 个字节 = 5 个 int 元素(1,2,3,4,5)
    memcpy(arr2, arr1, 20); 
    
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }
    return 0;
}

运行结果:

复制代码
1 2 3 4 5 0 0 0 0 0
  • 关键点:这里 num 传的是 20,因为 int 占 4 字节,5 个元素就是 5*4=20 字节。

三、模拟实现解析

复制代码
void * memcpy ( void * dst, const void * src, size_t count)
{
    void * ret = dst; // 保存目标起始地址,用于最后返回
    assert(dst);      // 断言:目标指针不能为空
    assert(src);      // 断言:源指针不能为空

    while (count--) {
        *(char *)dst = *(char *)src; // 按字节拷贝(char* 是 1 字节)
        dst = (char *)dst + 1;       // 目标指针向后移动 1 字节
        src = (char *)src + 1;       // 源指针向后移动 1 字节
    }
    return ret;
}
  • 为什么用 char* 转换因为 memcpy 是按字节拷贝的char* 每次移动 1 字节,刚好对应最小拷贝单位。
  • 为什么要保存 ret因为循环中 dst 会被不断修改 ,最后需要返回原始的目标起始地址

2.memmove

一、memmove 核心知识点

1. 函数原型

复制代码
void * memmove ( void * destination, const void * source, size_t num );

2. 功能说明

  • memcpy 一样,完成内存块拷贝,按字节复制数据。
  • 核心区别 :它专门处理源和目标内存重叠的场景,拷贝结果是安全、可预期的。
  • 头文件:#include <string.h>

3. 参数与返回值

memcpy 完全一致:

参数 含义
destination 目标内存地址(拷贝的数据存这里)
source 源内存地址(要拷贝的数据从这里来)
num 要拷贝的字节数
  • 返回值:拷贝完成后,返回目标空间的起始地址。

二、代码演示 & 运行结果分析

复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    memmove(arr1 + 2, arr1, 20); // 从 arr1 起始位置拷贝 20 字节到 arr1+2
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr1[i]);
    }
    return 0;
}

关键分析:

  • arr1int 数组,每个元素占 4 字节,20 字节对应 5 个元素
  • arr1 + 2 表示目标地址是数组第 3 个元素(索引 2)的位置。
  • 源地址是 arr1 (索引 0),目标地址 arr1+2 索引 2),两者内存是重叠的。
  • memmove 会安全地处理这种重叠,先把源数据完整保存再拷贝,不会覆盖还没复制的数据。

运行结果:

复制代码
1 2 1 2 3 4 5 8 9 10
  • 解析:arr1[0]~arr1[4] 的值 1,2,3,4,5 被拷贝到 arr1[2]~arr1[6] 的位置,数组后面的元素 6,7 被覆盖,变成了 1,2,3,4,5,所以输出如上。

三、模拟实现解析

复制代码
void * memmove ( void * dst, const void * src, size_t count)
{
    void * ret = dst;
    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目标地址在源前面 ) 或 dst >= src+count目标地址在源的拷贝范围之后 )→ 无重叠,按 memcpy 的方式从前向后拷贝即可。
    • else 条件:目标地址落在源的拷贝范围内 ,且在源的后面从后向前倒序拷贝,避免覆盖还没复制的数据。
  2. 为什么要分两种拷贝方向?

    • 正序拷贝:适用于无重叠场景高效简单
    • 倒序拷贝:专门解决目标在源后面的重叠场景,先复制后面的数据,再复制前面的数据,保证源数据不被提前覆盖

四、memcpy vs memmove 对比

特性 memcpy memmove
处理内存重叠 不支持,结果未定义 支持,结果安全
拷贝方向 固定从前向后 根据重叠情况自动选择正序 / 倒序
适用场景 源和目标内存完全独立 源和目标可能重叠
效率 略高(无需判断重叠) 略低(多了一次判断逻辑)

3.memset

一、 memset 函数基础

1. 函数原型

复制代码
void * memset ( void * ptr, int value, size_t num );

2. 核心功能

  • 用于按字节批量设置内存内容 ,将指定长度的内存块填充为特定值
  • 使用时必须包含头文件:#include <string.h>

3. 参数解析

参数 含义 细节
ptr 内存起始地址 指向要设置的内存空间的起始位置
value 要设置的值 函数会将其转换为**unsigned char** ,以字节为单位填充
num 内存长度 单位是字节控制填充的字节数

4. 返回值

返回 ptr(即被设置的内存块的起始地址)。


二、 代码演示解析

复制代码
#include <stdio.h>
#include <string.h>
int main()
{
    char str[] = "hello world";
    memset(str, 'x', 6);
    printf(str);
    return 0;
}

运行结果

输出:xxxxxxworld

原理说明

  • str 初始内容:h e l l o w o r l d注意 hello 后有一个空格,共 6 个字符)
  • memset(str, 'x', 6) 会从**str 起始地址开始** ,将前 6 个字节(h e l l o全部替换为 'x'
  • 因此输出的前 6 个字符变为 xxxxxx,后面的 world 保持不变

三、关键注意事项

  1. 按字节填充的本质 memset单字节操作,因此:

    • char 数组填充字符(如 'x')是安全的;
    • int 数组填充时,会将每个字节都设为 value 对应的 unsigned char 值。例如:int arr[5]; memset(arr, 0, sizeof(arr)); 可以将数组清 0 ,但 memset(arr, 1, sizeof(arr)); 不会把每个 int 设为 1,而是设为 0x01010101(小端 / 大端下的多字节组合值)。
  2. num 的单位是字节计算长度时要注意:

    • 对数组 arr,推荐写法:memset(arr, 0, sizeof(arr));自动计算总字节数
    • 避免硬编码错误,比如 int arr[10]; memset(arr, 0, 10); 只会清前 10 个字节 (前 2 个 int
  3. value 会被截断 传入的 int 类型 value 会被截断为 unsigned char ,因此传入 0x1234 实际效果和传入 0x34 是一样的。 0x1234 是一个 16 位的十六进制数 ,对应的二进制是:0001 0010 0011 0100 unsigned char****只能存 8 位 ,所以 memset 只会取它的低 8 位0011 0100也就是 0x34

4.memcmp

一、 memcmp 基础信息

函数原型

复制代码
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

核心功能

  • 按字节比较两块内存的内容 ,从 ptr1ptr2 指向的起始位置开始,比较连续 num 个字节
  • 头文件:#include <string.h>

参数说明

参数 含义
ptr1 指向第一块待比较内存的起始地址
ptr2 指向第二块待比较内存的起始地址
num 要比较的字节数,单位是字节

返回值规则

返回值 含义
< 0 ptr1 指向的内存小于 ptr2 (第一个不同字节的 unsigned char 值,ptr1 更小)
0 两块内存的num 个字节完全相同
> 0 ptr1 指向的内存大于 ptr2 (第一个不同字节的 unsigned char 值,ptr1 更大)

二、 代码演示解析

复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    char buffer1[] = "DWgaOtP12df0";
    char buffer2[] = "DWGAOTP12DF0";
    int n;

    n = memcmp(buffer1, buffer2, sizeof(buffer1));

    if (n > 0)
        printf("'%s' 大于 '%s'.\n", buffer1, buffer2);
    else if (n < 0)
        printf("'%s' 小于 '%s'.\n", buffer1, buffer2);
    else
        printf("'%s' 和 '%s' 一样.\n", buffer1, buffer2);

    return 0;
}

运行结果分析

我们逐字节对比**buffer1buffer2:**

  • 前两个字符 'D' 'W' 完全相同
  • 第三个字符:buffer1 是小写 'g'(ASCII 码 0x67),buffer2 是大写 'G' (ASCII 码 0x47
  • 因为**0x67 > 0x47** ,所以 memcmp 返回正数 ,程序会输出:'DWgaOtP12df0' 大于 'DWGAOTP12DF0'.

三、 关键细节与避坑

① 与 strcmp 的核心区别

函数 比较方式 终止条件 适用场景
memcmp 按字节比较,固定长度 必须比较完 num 个字节,不受 '\0' 影响 任意类型的内存块(如 int 数组、结构体)
strcmp 按字符比较,字符串专用 遇到 '\0' 就停止 仅用于比较 C 风格字符串

⚠️ 举个反例:如果字符串中间有**'\0'strcmp 会直接截断比较** ,而**memcmp 会继续比较后续字节。**

② 比较的是**unsigned char** 值

memcmp 会把每个字节当成 unsigned char 来比较,所以有符号字符的正负不影响比较结果 ,只看无符号的数值大小。

num 的单位是字节

  • 对数组比较时,推荐用 sizeof(数组) 自动计算长度,避免硬编码错误。
  • 例如:int arr1[5], arr2[5]; memcmp(arr1, arr2, sizeof(arr1)); 可以安全比较两个 int 数组的全部字节。
相关推荐
NNYSJYKJ1 小时前
K12 学习常见问题破解:脑能思维链的算法与教育应用
学习·算法
Shadow(⊙o⊙)1 小时前
硬核手搓解析!进程-内核分析:命令行参数及环境变量,重构main()
linux·运维·服务器·开发语言·c++·后端·学习
星夜夏空991 小时前
STM32单片机学习(11)——GPIO输入实验
stm32·单片机·学习
开开心心就好1 小时前
支持添加网址的资源快速打开工具
人工智能·学习·游戏·音视频·hbase·语音识别·storm
sheeta19982 小时前
TypeScript 学习笔记
笔记·学习·typescript
Honker_yhw2 小时前
大数据管理与应用系列丛书《数据挖掘》(吕欣等著)读书笔记-数据预处理
笔记·学习
sakiko_2 小时前
Swift学习笔记26-使用第三方库
笔记·学习·swift
Purple Coder10 小时前
BMS学习经验
学习
经济元宇宙11 小时前
摄影培训行业百科:机构选择与学习路径全解析
大数据·人工智能·学习