C语言学习笔记20260711-C语言动态内存管理四大核心函数详解及用例
核心头文件:
<stdlib.h>
适用场景:运行时才知道大小、需要跨函数共享数据、处理大型数据结构
一、四大核心函数详解
1. malloc - 分配未初始化内存
函数原型
void*malloc(size_tsize);功能说明
在堆区分配一块大小为size字节的连续内存,内存内容未初始化(垃圾值)。
代码示例
#include<stdio.h>#include<stdlib.h>intmain(void){// 分配10个int的空间int*arr=(int*)malloc(10*sizeof(int));// ⚠️ 必须检查返回值if(arr==NULL){perror("malloc failed");return1;}// 未初始化的内存是垃圾值,必须手动赋值for(inti=0;i<10;i++){arr[i]=i*2;}printf("arr = %d\n",arr);// 输出: arr = 10free(arr);arr=NULL;// 防止悬空指针return0;}关键要点
- 返回
void*,C语言中可隐式转换,但建议显式强转提高可读性 - 分配失败返回
NULL,必须检查 - 内存内容是未定义的垃圾值
2. calloc - 分配并清零内存
函数原型
void*calloc(size_tnum,size_tsize);功能说明
分配num个大小为size字节的内存块,并自动将所有字节初始化为0。
代码示例
#include<stdio.h>#include<stdlib.h>intmain(void){// 分配10个int,且全部初始化为0int*arr=(int*)calloc(10,sizeof(int));if(arr==NULL){perror("calloc failed");return1;}// 验证是否全为0for(inti=0;i<10;i++){printf("%d ",arr[i]);// 输出: 0 0 0 0 0 0 0 0 0 0}printf("\n");free(arr);arr=NULL;return0;}关键要点
- 等价于
malloc(num * size)+memset(ptr, 0, num * size) - 适合数组、结构体、哈希表等需要清零的场景
- 性能略低于
malloc(多了清零操作)
3. realloc - 调整内存大小
函数原型
void*realloc(void*ptr,size_tsize);功能说明
调整已分配内存块的大小,行为逻辑如下:
| 参数情况 | 等价操作 | 说明 |
|---|---|---|
ptr == NULL | malloc(size) | 等同于分配新内存 |
size == 0且ptr != NULL | free(ptr) | 等同于释放内存 |
| 原空间足够 | 原地扩展 | 返回原指针 |
| 原空间不足 | 重新分配+拷贝+释放旧块 | 返回新指针 |
⚠️ 致命陷阱
如果realloc失败返回NULL,原内存块依然有效且未释放。直接用原指针接收返回值会导致内存泄漏。
代码示例
#include<stdio.h>#include<stdlib.h>intmain(void){int*arr=(int*)malloc(5*sizeof(int));if(arr==NULL)return1;for(inti=0;i<5;i++)arr[i]=i;// ✅ 正确用法:用临时指针接收返回值int*tmp=(int*)realloc(arr,10*sizeof(int));if(tmp==NULL){perror("realloc failed");free(arr);// realloc失败时,原内存仍有效,必须手动释放return1;}arr=tmp;// 确认成功后再赋值给原指针// 新增的空间内容是未初始化的for(inti=5;i<10;i++)arr[i]=i*10;for(inti=0;i<10;i++)printf("%d ",arr[i]);printf("\n");free(arr);arr=NULL;return0;}关键要点
- 永远用临时指针接收返回值
- 扩容后新增的内存未初始化
- 缩容时数据会被截断,但不会丢失已保留部分
4. free - 释放内存
函数原型
voidfree(void*ptr);功能说明
释放之前由malloc/calloc/realloc分配的堆内存。
代码示例
#include<stdio.h>#include<stdlib.h>intmain(void){int*p=(int*)malloc(sizeof(int));if(p==NULL)return1;*p=42;printf("*p = %d\n",*p);free(p);p=NULL;// ✅ 释放后立即置NULL,防止悬空指针// free(p); // ❌ 如果p没置NULL,这里会崩溃(重复释放)free(p);// ✅ free(NULL) 是安全的,什么都不做return0;}关键要点
- 只能释放堆内存,不能释放栈变量、全局变量、字符串常量
free(NULL)是安全操作,什么都不做- 重复释放是未定义行为,会导致堆损坏
- 释放后指针变成悬空指针,必须置
NULL
二、常见错误与最佳实践
错误速查表
| 错误类型 | 说明 | 后果 | 预防措施 |
|---|---|---|---|
| 未检查返回值 | malloc失败返回NULL,直接使用 | 段错误 | 每次分配后必须检查== NULL |
| 内存泄漏 | 分配后未free,或指针丢失 | 内存耗尽 | 遵循"谁分配谁释放"原则 |
| 悬空指针 | free后未置NULL,再次使用 | 未定义行为 | free(p); p = NULL;形成肌肉记忆 |
| 重复释放 | 同一指针free两次 | 堆损坏/崩溃 | 置NULL后free是安全的 |
| 越界访问 | 读写超出分配大小的内存 | 未定义行为 | 始终记录分配大小,使用工具检测 |
| realloc 错误用法 | ptr = realloc(ptr, size) | 失败时内存泄漏 | 用临时指针接收,成功后再赋值 |
| 释放栈内存 | free(&local_var) | 崩溃 | 只释放堆内存 |
| 释放后使用 | 访问已free的内存 | 未定义行为 | 释放后立即置NULL |
最佳实践清单
- 分配必检查:每次
malloc/calloc/realloc后检查返回值 - 释放必置空:
free(p); p = NULL;写成固定套路 - realloc用临时指针:避免失败时内存泄漏
- 记录分配大小:用变量或结构体成员保存大小,避免越界
- 谁分配谁释放:明确内存所有权,避免重复释放或泄漏
- 函数返回前检查:确保所有路径都正确释放内存
- 使用工具检测:不要依赖肉眼排查内存问题
三、实战综合示例
动态数组封装
#include<stdio.h>#include<stdlib.h>#include<string.h>typedefstruct{int*data;size_tsize;size_tcapacity;}DynArray;// 初始化intdynarray_init(DynArray*arr,size_tinit_cap){arr->data=(int*)calloc(init_cap,sizeof(int));if(arr->data==NULL)return-1;arr->size=0;arr->capacity=init_cap;return0;}// 追加元素intdynarray_push(DynArray*arr,intvalue){if(arr->size>=arr->capacity){size_tnew_cap=arr->capacity*2;int*tmp=(int*)realloc(arr->data,new_cap*sizeof(int));if(tmp==NULL)return-1;arr->data=tmp;arr->capacity=new_cap;}arr->data[arr->size++]=value;return0;}// 释放voiddynarray_free(DynArray*arr){free(arr->data);arr->data=NULL;arr->size=0;arr->capacity=0;}intmain(void){DynArray arr;if(dynarray_init(&arr,4)!=0){perror("init failed");return1;}for(inti=0;i<10;i++){if(dynarray_push(&arr,i*i)!=0){perror("push failed");dynarray_free(&arr);return1;}}for(size_ti=0;i<arr.size;i++){printf("%d ",arr.data[i]);}printf("\n");dynarray_free(&arr);return0;}四、总结口诀
分配必检查,释放必置空。
Realloc 用临时,越界要命凶。
谁分配谁释放,工具勤检测。
堆上无小事,谨慎保平安。
最后更新:2026-07-11