内存操作函数的头文件:

  • 动态内存分配 / 释放:<stdlib.h>
  • 内存内容操作(拷贝、填充、比较等):<string.h>

第一类:动态内存分配与释放函数

这类函数的核心是管理堆内存(堆内存手动申请、手动释放,不随函数栈帧销毁),是实现动态数据结构(如动态顺序表、链表)的基础。

1. malloc():首次申请未初始化堆内存

  • 原型:void *malloc(size_t size);
  • 功能:从堆区申请一块size字节的连续未初始化内存
  • 返回值:成功返回内存首地址(void*),失败返回NULL
  • 特点:内存中是随机 “垃圾值”,不做任何初始化;size=0行为未定义(不推荐使用)
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 需求:存储5个学生的成绩(int类型)
    int student_count = 5;
    // 1. 计算所需字节数:元素个数 * 单个元素字节数(sizeof(int))
    // 强制转换为int*,方便后续数组操作;void* -> int*
    int* scores = (int*)malloc(student_count * sizeof(int));
    
    // 2. 关键:必须检查malloc返回值是否为NULL(避免空指针解引用崩溃)
    if (scores == NULL) {
        perror("malloc 内存分配失败"); // 输出具体错误原因
        return 1; // 异常退出程序
    }
    
    // 3. 手动初始化内存(malloc返回的是随机垃圾值,必须手动赋值/初始化)
    for (int i = 0; i < student_count; i++) {
        scores[i] = 80 + i; // 赋值:80, 81, 82, 83, 84
    }
    
    // 4. 使用内存
    printf("学生成绩列表:");
    for (int i = 0; i < student_count; i++) {
        printf("%d ", scores[i]);
    }
    printf("\n");
    
    // 5. 关键:使用完堆内存必须释放,避免内存泄漏
    free(scores);
    // 6. 关键:释放后将指针置为NULL,避免野指针(后续误操作该指针)
    scores = NULL;
    
    return 0;
}

2. calloc():首次申请并初始化清零的堆内存

  • 原型:void *calloc(size_t num, size_t size);
  • 功能:从堆区申请num个 “每个大小为size字节” 的连续内存,并将所有字节初始化为 0
  • 返回值:成功返回内存首地址(void*),失败返回NULL
  • 等价于:malloc(num * size) + memset(返回指针, 0, num * size)(效率略高于单独调用两者)
malloc()区别
特性 malloc() calloc()
初始化状态 未初始化(垃圾值) 全部清零(0)
参数形式 直接传入总字节数 传入 “元素个数”+“单个元素字节数”
适用场景 无需初始化的场景(如后续立即赋值) 需要初始化为 0 的场景(如数组、结构体)

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 需求:存储10个用户的积分(初始值为0,后续按需修改)
    int user_count = 10;
    // 1. calloc参数:(元素个数, 单个元素字节数),自动将所有字节置为0
    int* user_scores = (int*)calloc(user_count, sizeof(int));
    
    // 2. 关键:检查返回值是否为NULL
    if (user_scores == NULL) {
        perror("calloc 内存分配失败");
        return 1;
    }
    
    // 3. 验证:calloc返回的内存已自动清零(无需手动初始化)
    printf("初始积分列表(前5个):");
    for (int i = 0; i < 5; i++) {
        printf("%d ", user_scores[i]); // 输出:0 0 0 0 0
    }
    printf("\n");
    
    // 4. 按需修改部分数据
    user_scores[0] = 100; // 第1个用户积分100
    user_scores[3] = 50;  // 第4个用户积分50
    
    // 5. 查看修改后结果
    printf("修改后积分列表(前5个):");
    for (int i = 0; i < 5; i++) {
        printf("%d ", user_scores[i]); // 输出:100 0 0 50 0
    }
    printf("\n");
    
    // 6. 释放内存+置空指针
    free(user_scores);
    user_scores = NULL;
    
    return 0;
}

3. realloc():调整已分配堆内存的大小

  • 原型:void *realloc(void *ptr, size_t size);
  • 功能:调整ptr指向的堆内存大小为size字节(可扩容、可缩容)
  • 返回值:成功返回新内存地址(可能与原地址相同),失败返回NULL(原内存仍有效)
  • 关键特点:
    1. 原地调整:原内存后有足够空间,直接扩展,效率高;
    2. 异地调整:原内存后无空间,申请新内存→复制原数据→释放旧内存,效率低;
    3. ptr=NULL,等价于malloc(size)
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 步骤1:先用malloc申请初始内存(4个int,存储4个数据)
    int* arr = (int*)malloc(4 * sizeof(int));
    if (arr == NULL) {
        perror("malloc 初始分配失败");
        return 1;
    }
    
    // 初始化初始内存
    for (int i = 0; i < 4; i++) {
        arr[i] = i + 1; // 1, 2, 3, 4
    }
    printf("初始数组:");
    for (int i = 0; i < 4; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n原内存地址:%p\n", arr);
    
    // 步骤2:需求变更,需要存储8个数据,用realloc扩容
    int new_size = 8;
    // 关键避坑:用临时变量new_arr接收realloc返回值,避免扩容失败丢失原内存
    int* new_arr = (int*)realloc(arr, new_size * sizeof(int));
    if (new_arr == NULL) {
        perror("realloc 扩容失败");
        // 扩容失败时,原arr仍然有效,需手动释放,避免内存泄漏
        free(arr);
        arr = NULL;
        return 1;
    }
    
    // 步骤3:扩容成功,更新指针(原arr已被realloc自动释放,无需再free)
    arr = new_arr;
    new_arr = NULL; // 临时变量置空,避免野指针
    
    // 步骤4:初始化新增的4个元素(下标4~7)
    for (int i = 4; i < new_size; i++) {
        arr[i] = i + 1; // 5, 6, 7, 8
    }
    
    // 步骤5:查看扩容后结果
    printf("扩容后数组:");
    for (int i = 0; i < new_size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n新内存地址:%p\n", arr); // 可能与原地址相同(原地扩容)或不同(异地扩容)
    
    // 步骤6:释放最终内存+置空
    free(arr);
    arr = NULL;
    
    return 0;
}

4. free():释放已分配的堆内存

  • 原型:void free(void *ptr);
  • 功能:释放ptr指向的堆内存(将内存归还系统,不可再访问)
  • 无返回值,核心是 “内存释放” 而非 “指针置空”
关键注意事项
  1. ptr必须是malloc()/calloc()/realloc()返回的有效指针,不可释放栈内存;
  2. 释放后必须将ptr置为NULLptr=NULL;),避免野指针;
  3. 不可重复释放(对同一已释放指针再次free(),程序崩溃);
  4. ptr=NULL时,free(NULL)是安全的(无任何操作)。
#include <stdio.h>
#include <stdlib.h>

int main() {
    int* ptr = NULL; // 初始化指针为NULL,避免野指针
    
    // 1. 申请内存
    ptr = (int*)malloc(10 * sizeof(int));
    if (ptr == NULL) {
        perror("malloc 分配失败");
        return 1;
    }
    printf("内存申请成功,指针地址:%p\n", ptr);
    
    // 2. 使用内存(简单赋值)
    ptr[0] = 100;
    printf("ptr[0] = %d\n", ptr[0]);
    
    // 3. 正确释放内存
    // 关键:释放前可判断指针是否为NULL(free(NULL)是安全的,无任何操作)
    if (ptr != NULL) {
        free(ptr); // 释放堆内存,将内存归还系统
        printf("内存释放成功\n");
        ptr = NULL; // 核心:释放后立即置为NULL,避免野指针
    }
    
    // 4. 避坑:重复释放(此时ptr已为NULL,free(NULL)安全,不会崩溃)
    free(ptr); // 无任何操作,不会报错
    // 若未将ptr置为NULL,此处重复释放已释放的内存,会导致程序崩溃
    
    // 5. 避坑:野指针操作(此时ptr为NULL,解引用会崩溃,需避免)
    // printf("%d\n", ptr[0]); // 注释掉,否则程序崩溃
    
    return 0;
}

第二类:内存内容操作函数

这类函数不管理内存的 “生死”,只对已分配的有效内存进行数据操作(拷贝、填充、比较等),核心是 “逐字节操作”,支持任意数据类型。

1. memcpy():内存拷贝(不处理重叠)

  • 原型:void *memcpy(void *dest, const void *src, size_t n);
  • 功能:从src指向的内存,逐字节拷贝n个字节到dest指向的内存
  • 返回值:成功返回dest(目标内存地址),方便链式调用
  • 关键特点:不处理内存重叠(重叠时拷贝结果未定义),不识别'\0',仅按n字节拷贝
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 定义一个自定义结构体(展示memcpy支持任意数据类型)
typedef struct {
    char name[20];
    int age;
    float score;
} Student;

int main() {
    // 案例1:拷贝结构体(核心场景,strcpy无法直接拷贝结构体)
    Student stu1 = {"张三", 18, 95.5};
    Student stu2; // 未初始化的目标结构体
    
    // 1. memcpy拷贝结构体:字节数=sizeof(Student),完整拷贝所有成员
    memcpy(&stu2, &stu1, sizeof(Student));
    printf("结构体拷贝结果:\n");
    printf("姓名:%s,年龄:%d,分数:%.1f\n", stu2.name, stu2.age, stu2.score);
    
    // 案例2:拷贝无重叠int数组
    int src_arr[] = {1, 2, 3, 4, 5};
    int dest_arr[5] = {0}; // 目标数组初始化为0
    
    // 2. memcpy拷贝数组:字节数=5*sizeof(int)
    memcpy(dest_arr, src_arr, 5 * sizeof(int));
    printf("\n数组拷贝结果:");
    for (int i = 0; i < 5; i++) {
        printf("%d ", dest_arr[i]); // 输出:1 2 3 4 5
    }
    printf("\n");
    
    return 0;
}

2. memmove():内存拷贝(处理重叠,安全)

  • 原型:void *memmove(void *dest, const void *src, size_t n);
  • 功能:与memcpy()功能一致,额外处理内存重叠场景,拷贝结果安全可靠
  • 返回值:成功返回dest
  • 工作原理:
    1. destsrc前面(无重叠或部分重叠),从前往后拷贝;
    2. destsrc后面(重叠),从后往前拷贝,避免数据覆盖。
memcpy()区别
特性 memcpy() memmove()
内存重叠处理 不处理(结果未定义) 处理(安全可靠)
效率 略高(无额外判断逻辑) 略低(需判断拷贝方向)
适用场景 确定内存无重叠的拷贝 不确定是否重叠(如数组内部拷贝)、必须安全拷贝的场景
#include <stdio.h>
#include <string.h>

int main() {
    // 场景:将数组中从下标2开始的5个元素,拷贝到下标0开始的位置(内存重叠)
    char arr[] = "1234567890"; // 原数组:索引0~9
    printf("拷贝前数组:%s\n", arr); // 输出:1234567890
    
    // 关键:src=arr+2("34567"),dest=arr("12345"),两者内存重叠
    // 若用memcpy,结果未定义;用memmove,结果安全可靠
    memmove(arr, arr + 2, 5); // 拷贝5个字节:从arr+2到arr
    
    printf("拷贝后数组:%s\n", arr); // 输出:3456767890(正确结果)
    
    // 对比:若用memcpy(arr, arr+2, 5),可能输出乱码或不可预测结果
    return 0;
}

3. memset():内存字节填充(快速初始化)

  • 原型:void *memset(void *s, int c, size_t n);
  • 功能:将s指向的内存的n个字节,全部填充为cc会被强制转换为unsigned char
  • 返回值:成功返回s(目标内存地址)
  • 关键特点:逐字节填充
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    // 案例1:正确用法:字符数组填充(逐字节填充,匹配字符类型)
    char str[20];
    // 将str前8个字节填充为'a',剩余字节为随机值
    memset(str, 'a', 8);
    str[8] = '\0'; // 手动添加字符串终止符,避免打印乱码
    printf("字符数组填充结果:%s\n", str); // 输出:aaaaaaaa
    
    // 案例2:正确用法:内存清零(填充0,适合任意数据类型)
    int* arr = (int*)malloc(5 * sizeof(int));
    if (arr == NULL) {
        perror("malloc 分配失败");
        return 1;
    }
    memset(arr, 0, 5 * sizeof(int)); // 所有字节置为0,int值即为0
    printf("内存清零结果(前3个int):%d %d %d\n", arr[0], arr[1], arr[2]); // 输出:0 0 0
    
    // 案例3:经典坑点:不可用memset填充非0的int数组(逐字节填充,非整个int值)
    memset(arr, 1, 5 * sizeof(int)); // 错误!不是将int设为1
    // 每个int占4字节,每个字节被填充为0x01,最终int值为0x01010101(十进制268435457)
    printf("非0int填充错误结果:%d\n", arr[0]); // 输出:268435457
    
    // 释放内存
    free(arr);
    arr = NULL;
    
    return 0;
}

4. memcmp():内存逐字节比较

  • 原型:int memcmp(const void *s1, const void *s2, size_t n);
  • 功能:逐字节比较s1s2指向的内存的n个字节
  • 返回值(按无符号字符比较):
    1. 小于 0:s1的第一个不同字节 < s2的对应字节;
    2. 等于 0:s1s2的前n个字节完全相同;
    3. 大于 0:s1的第一个不同字节 > s2的对应字节。
  • 关键特点:不识别'\0',仅按n字节比较,支持任意数据类型(数组、结构体等)。
strcmp()区别
特性 memcmp() strcmp()
比较终止条件 比较满n个字节或找到不同字节 遇到'\0'停止或找到不同字节
适用数据类型 任意类型(数组、结构体等) 仅字符串
比较规则 按无符号字符逐字节比较 按 ASCII 码逐字符比较
#include <stdio.h>
#include <string.h>

// 定义结构体
typedef struct {
    int id;
    char name[10];
} User;

int main() {
    // 案例1:比较两个结构体是否完全相同
    User user1 = {1001, "张三"};
    User user2 = {1001, "张三"};
    User user3 = {1002, "李四"};
    
    // 比较整个结构体:字节数=sizeof(User)
    int ret1 = memcmp(&user1, &user2, sizeof(User));
    int ret2 = memcmp(&user1, &user3, sizeof(User));
    
    printf("user1 与 user2 比较结果:%d(0=相同)\n", ret1); // 输出:0
    printf("user1 与 user3 比较结果:%d(非0=不同)\n", ret2); // 输出:负数(1001 < 1002)
    
    // 案例2:比较字符串前n个字节(不依赖'\0',部分比较)
    char str1[] = "hello world";
    char str2[] = "hello china";
    
    // 仅比较前5个字节("hello"),忽略后续差异
    int ret3 = memcmp(str1, str2, 5);
    printf("str1 与 str2 前5个字节比较结果:%d\n", ret3); // 输出:0(相同)
    
    // 比较前6个字节("hello " vs "hello c")
    int ret4 = memcmp(str1, str2, 6);
    printf("str1 与 str2 前6个字节比较结果:%d\n", ret4); // 输出:负数('w' < 'c',按无符号字符比较)
    
    return 0;
}

5. memchr():内存字节查找

  • 原型:void *memchr(const void *s, int c, size_t n);
  • 功能:在s指向的内存的n个字节中,查找第一个值为c(强制转换为unsigned char)的字节
  • 返回值:成功返回指向该字节的指针,失败返回NULL
  • 特点:不识别'\0',仅按n字节查找,支持任意数据类型。
#include <stdio.h>
#include <string.h>

int main() {
    // 案例1:在字符串中查找指定字符('w')
    char str[] = "hello world, welcome to C language";
    char target_char = 'w';
    
    // 查找第一个'w',查找范围:整个字符串(strlen(str)+1,包含'\0')
    char* char_ptr = (char*)memchr(str, target_char, strlen(str) + 1);
    if (char_ptr != NULL) {
        printf("找到字符 '%c',位置:%ld(从0开始)\n", target_char, char_ptr - str);
        printf("从找到位置开始的字符串:%s\n", char_ptr);
    } else {
        printf("未找到字符 '%c'\n", target_char);
    }
    
    // 案例2:在int数组中查找指定字节(查找值为1的字节)
    int arr[] = {0x01020304, 0x05060708};
    int target_byte = 0x03;
    
    // 查找第一个0x03字节,查找范围:整个数组(sizeof(arr))
    void* byte_ptr = memchr(arr, target_byte, sizeof(arr));
    if (byte_ptr != NULL) {
        printf("\n找到字节 0x%02X,偏移量:%ld 字节\n", target_byte, (char*)byte_ptr - (char*)arr);
    } else {
        printf("\n未找到字节 0x%02X\n", target_byte);
    }
    
    return 0;
}

所有内存操作函数的通用注意事项

  1. 检查NULL返回值malloc()/calloc()/realloc()/memchr()可能返回NULL,直接解引用会导致程序崩溃;
  2. 避免内存越界:所有操作(memcpy()/memmove()/memset()等)的n不能超过目标内存的大小,否则破坏堆区数据;
  3. 堆内存必须释放malloc()/calloc()/realloc()申请的内存,使用完必须用free()释放,避免内存泄漏;
  4. void*指针转换:C 语言中可自动转换为其他类型指针,C++ 中必须显式强制转换(推荐 C 语言也显式转换,提高可读性);
  5. 内存重叠仅用memmove():不确定内存是否重叠时,优先使用memmove(),牺牲少量效率换取安全性;
  6. memset()仅适合字节级初始化:填充非 0 的非字符类型数据(如int),避免使用memset(),手动循环赋值更安全。

总结表格

函数名 头文件 核心功能 关键特点
malloc() <stdlib.h> 申请未初始化堆内存 未清零,返回NULL需检查
calloc() <stdlib.h> 申请并清零堆内存 元素个数 + 单个大小,等价于malloc()+memset()
realloc() <stdlib.h> 调整已分配堆内存大小 支持扩容 / 缩容,用临时变量接收返回值
free() <stdlib.h> 释放堆内存 释放后置NULL,不可重复释放
memcpy() <string.h> 内存拷贝 不处理重叠,效率略高
memmove() <string.h> 安全内存拷贝 处理重叠,安全可靠
memset() <string.h> 内存字节填充 逐字节操作,适合清零或字符填充
memcmp() <string.h> 内存逐字节比较 不识别'\0',支持任意数据类型
memchr() <string.h> 内存字节查找 查找第一个匹配字节,返回指针
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐