内存操作函数
函数名头文件核心功能关键特点malloc()<stdlib.h>申请未初始化堆内存未清零,返回NULL需检查calloc()<stdlib.h>申请并清零堆内存元素个数 + 单个大小,等价于realloc()<stdlib.h>调整已分配堆内存大小支持扩容 / 缩容,用临时变量接收返回值free()<stdlib.h>释放堆内存释放后置NULL,不可重复释放memcpy()<string.h>内存
·
内存操作函数的头文件:
- 动态内存分配 / 释放:
<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(原内存仍有效) - 关键特点:
- 原地调整:原内存后有足够空间,直接扩展,效率高;
- 异地调整:原内存后无空间,申请新内存→复制原数据→释放旧内存,效率低;
- 若
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指向的堆内存(将内存归还系统,不可再访问) - 无返回值,核心是 “内存释放” 而非 “指针置空”
关键注意事项
ptr必须是malloc()/calloc()/realloc()返回的有效指针,不可释放栈内存;- 释放后必须将
ptr置为NULL(ptr=NULL;),避免野指针; - 不可重复释放(对同一已释放指针再次
free(),程序崩溃); 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 - 工作原理:
- 若
dest在src前面(无重叠或部分重叠),从前往后拷贝; - 若
dest在src后面(重叠),从后往前拷贝,避免数据覆盖。
- 若
与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个字节,全部填充为c(c会被强制转换为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); - 功能:逐字节比较
s1和s2指向的内存的前n个字节 - 返回值(按无符号字符比较):
- 小于 0:
s1的第一个不同字节 <s2的对应字节; - 等于 0:
s1和s2的前n个字节完全相同; - 大于 0:
s1的第一个不同字节 >s2的对应字节。
- 小于 0:
- 关键特点:不识别
'\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;
}
所有内存操作函数的通用注意事项
- 检查
NULL返回值:malloc()/calloc()/realloc()/memchr()可能返回NULL,直接解引用会导致程序崩溃; - 避免内存越界:所有操作(
memcpy()/memmove()/memset()等)的n不能超过目标内存的大小,否则破坏堆区数据; - 堆内存必须释放:
malloc()/calloc()/realloc()申请的内存,使用完必须用free()释放,避免内存泄漏; void*指针转换:C 语言中可自动转换为其他类型指针,C++ 中必须显式强制转换(推荐 C 语言也显式转换,提高可读性);- 内存重叠仅用
memmove():不确定内存是否重叠时,优先使用memmove(),牺牲少量效率换取安全性; 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> |
内存字节查找 | 查找第一个匹配字节,返回指针 |
更多推荐



所有评论(0)