一、内存布局底层逻辑
C 语言程序运行时,内存会被划分成 5 个核心区域,不同区域的内存,生命周期、分配方式、使用规则完全不同。
| 内存区域 | 存储内容 | 生命周期 | 分配 / 释放方式 | 大小限制 | 核心特点 |
|---|---|---|---|---|---|
| 代码区 | 编译后的机器指令(main等函数) | 程序运行期间存在 | 操作系统自动分配释放 | 通常较小 | 只读(写操作会触发段错误) |
| 常量存储区 | 字符串常量(如"hello")、const全局变量 | 程序运行期间存在 | 操作系统自动分配释放 | 较小 | 只读(修改会触发段错误) |
| 全局 / 静态存储区 | 全局变量)、静态变量 | 程序运行期间存在 | 操作系统自动分配释放 | 中等 | 未初始化的全局 / 静态变量会默认置 0 |
| 堆(Heap) | 动态申请的内存(如数组、结构体) | 手动控制(malloc→free) | 程序员手动分配(malloc等)、释放(free) | 较大(取决于物理内存) | 内存碎片多,需手动管理生命周期 |
| 栈(Stack) | 局部变量(int a;)、函数参数、返回地址 | 随函数调用创建,函数返回销毁 | 编译器自动分配释放 | 较小(默认几 MB,可配置) | 先进后出(栈帧机制),溢出会崩溃 |
二、动态内存管理
C 语言的动态内存管理,本质是 “操作堆内存”,通过malloc/calloc/realloc/free,实现堆内存的分配、扩容、释放。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 1 malloc:分配未初始化内存
int* malloc_arr = (int*)malloc(5 * sizeof(int));
if (malloc_arr == NULL) {
perror("malloc failed");
return 1;
}
printf("=== malloc示例 ===\n");
for (int i = 0; i < 5; i++) {
malloc_arr[i] = i * 10; // 手动初始化
printf("%d ", malloc_arr[i]); // 输出:0 10 20 30 40
}
printf("\n\n");
// 2 calloc:分配并初始化为0的内存
double* calloc_arr = (double*)calloc(3, sizeof(double));
if (calloc_arr == NULL) {
perror("calloc failed");
free(malloc_arr); // 释放已分配内存
return 1;
}
printf("=== calloc示例 ===\n");
for (int i = 0; i < 3; i++) {
printf("%.1f ", calloc_arr[i]); // 输出:0.0 0.0 0.0(自动初始化)
}
printf("\n\n");
// 3 realloc:调整已分配内存大小(将malloc_arr从5个int扩容到8个)
int* realloc_arr = (int*)realloc(malloc_arr, 8 * sizeof(int));
if (realloc_arr == NULL) {
perror("realloc failed");
free(malloc_arr); // 扩容失败时释放原内存
free(calloc_arr);
return 1;
}
// 原内存地址可能改变,更新指针
malloc_arr = realloc_arr;
printf("=== realloc示例 ===\n");
// 初始化新增的3个元素
for (int i = 5; i < 8; i++) {
malloc_arr[i] = i * 10;
}
for (int i = 0; i < 8; i++) {
printf("%d ", malloc_arr[i]); // 输出:0 10 20 30 40 50 60 70
}
printf("\n\n");
// 4 free:释放所有动态分配的内存
printf("=== free示例 ===\n");
free(malloc_arr);
malloc_arr = NULL; // 释放后置空,避免野指针
free(calloc_arr);
calloc_arr = NULL;
printf("所有动态内存已释放\n");
return 0;
}
三、陷阱
C 语言内存管理的难点在于如何避免错误使用。
1. 内存泄漏(最常见,隐蔽性强)
堆内存分配后(malloc/calloc/realloc),未用 free 释放,且丢失了指向该内存的所有指针,导致系统无法回收,内存被永久占用。
void func() {
int* p = (int*)malloc(10 * sizeof(int));
// 业务逻辑...
return; // 错误!p指向的堆内存未释放,函数返回后p销毁,内存泄漏
}
后果:短期程序影响不大;长期运行的程序(如服务器)会持续占用内存,最终耗尽堆内存,导致程序崩溃。
规避方法
遵循 “谁分配,谁释放” 原则:分配内存的模块 / 函数,负责释放。
2. 野指针
指向 “无效内存” 的指针:可能是 free 后未置 NULL 的指针,或未初始化的指针(随机指向某块内存)。
int* p = (int*)malloc(sizeof(int));
free(p); // 释放内存,但p仍指向原地址
*p = 10; // 错误!操作无效内存,可能崩溃或篡改其他数据
int* p; // 未初始化,p的值是随机的(野指针)
*p = 20; // 错误!随机操作内存,后果不可控
规避方法
free 后立即将指针置为 NULL(如 free(p); p = NULL;)。
指针声明时,若暂时不分配内存,先置为 NULL(如 int* p = NULL;)。
使用指针前,先判断是否为 NULL(如 if (p != NULL) { *p = ...; })。
3. 内存越界访问
访问的内存超出了分配的堆内存范围。
int* arr = (int*)malloc(10 * sizeof(int)); // 0~9索引有效
for (int i = 0; i <= 10; i++) {
arr[i] = i; // 错误!i=10时越界,篡改相邻内存数据
}
后果:可能暂时不崩溃,但会 “污染” 相邻的堆内存(篡改其他变量数据),导致程序逻辑错误;严重时直接触发段错误。
规避方法
严格控制数组 / 内存的访问索引。
4. 重复释放
同一堆内存被 free 多次。
int* p = (int*)malloc(sizeof(int));
free(p); // 第一次释放
// ... 中间逻辑未置p=NULL
free(p); // 错误!第二次释放,程序崩溃
规避方法
free 后立即将指针置为 NULL,后续再 free(p) 时,free(NULL) 是安全的。
5. 释放非堆内存
用 free 释放栈内存、全局变量、常量内存等非堆内存。
// 场景1:释放栈内存(局部变量)
int a = 5;
free(&a); // 错误!
// 场景2:释放全局变量(全局区)
int g_val = 10;
free(&g_val); // 错误!
// 场景3:释放常量内存(常量区)
char* str = "hello"; // "hello"在常量区,str是栈上的指针
free(str); // 错误!
6. 未初始化内存的使用
使用 malloc 分配的内存(未初始化,垃圾值),未手动赋值就直接读取。
int* p = (int*)malloc(sizeof(int));
printf("%d\n", *p); // 错误!读取未初始化的内存,输出随机值
后果:程序逻辑错误(依赖随机值),结果不可控。
规避方法
分配后立即手动赋值。

2097

被折叠的 条评论
为什么被折叠?



