C语言 内存管理
一、程序内存五大分区(Linux 64 位系统)
程序运行时操作系统会划分 5 块独立内存区域,分区特性自动 / 手动管理区分明显:
-
内核区 操作系统专属空间,用户代码无权限读写。
-
栈区(stack)
-
存储内容:局部变量、函数形参、函数返回地址、函数调用现场保护数据
-
管理方式:操作系统自动分配释放,函数执行完毕自动回收
-
限制:默认大小 8M,空间很小,不适合存储大容量数据
-
特性:读写速度快,内存连续向下分配
-
-
堆区(heap)
-
存储内容:手动申请的动态内存(
malloc/calloc/realloc开辟) -
管理方式:程序员手动申请、手动 free 释放
-
特点:空间巨大,生命周期不受函数限制;忘记释放会造成内存泄漏
-
分配规则:内存向上分配,碎片化问题
-
-
数据区(全局静态区) 分为 3 个子段:
-
.data段:已初始化的全局变量、static 静态变量 -
.bss段:未初始化全局 /static 静态变量,程序加载时系统自动清 0 -
字符串常量区:存放
"abc"这类双引号字符串,只读不可修改
-
-
文本区(代码段 .text) 存放编译后的二进制指令、常量代码,只读,防止程序被篡改。
栈区 vs 堆区 对比表
|
对比项 |
栈 stack |
堆 heap |
|
管理者 |
操作系统自动管理 |
程序员手动 malloc/free |
|
生命周期 |
函数结束立即回收 |
不 free 则持续占用直到程序退出 |
|
空间大小 |
默认 8M,容量小 |
操作系统剩余内存,容量极大 |
|
读写速度 |
速度更快 |
速度慢于栈 |
|
内存碎片 |
几乎无碎片 |
频繁开辟释放容易产生碎片 |
|
适用场景 |
临时小变量、函数参数 |
大容量数组、跨函数长期使用数据 |
二、动态内存分配(堆区核心 API)
头文件必须引入:#include <stdlib.h>
1. malloc 开辟内存
void *malloc(size_t size);
-
参数
size:需要申请的字节总数 -
返回值:成功返回堆空间首地址;失败返回
NULL -
特点:申请的内存不会自动清零,内部是随机脏数据 示例:
// 申请4字节int空间 int *p = malloc(sizeof(int)); if(p == NULL) // 必须判空,防止开辟失败 { perror("malloc fail"); return -1; } *p = 100; free(p); // 使用完释放
2. calloc 批量开辟并清零
void *calloc(size_t nmemb, size_t size);
-
参数 1
nmemb:元素个数;参数 2size:单个元素字节大小 -
功能:开辟
nmemb * size字节堆内存,自动全部初始化为 0 示例:
// 开辟10个int数组空间,全部置0 int *arr = calloc(10, sizeof(int)); free(arr);
3. realloc 扩容 / 缩容已有堆内存
void *realloc(void *ptr, size_t size);
-
参数
ptr:已经通过 malloc/calloc 开辟的堆指针;size:新总字节数 -
逻辑:
-
原空间后方有足够空闲内存:直接向后扩容,返回原指针
-
后方空间不足:开辟一块全新 size 空间,拷贝旧数据,释放旧空间,返回新地址
-
size 缩小:截断尾部多余空间,保留前面数据
-
-
注意:不能直接
ptr = realloc(ptr, new_size),开辟失败会覆盖原有指针导致丢失 标准写法:
int *tmp = realloc(arr, 20 * sizeof(int)); if(tmp != NULL) { arr = tmp; }
4. free 释放堆内存
void free(void *ptr);
-
参数:malloc/calloc/realloc 返回的堆首地址
-
规则:
-
同一块堆内存只能 free 一次,重复 free 程序崩溃
-
free 后指针不会自动置空,变成野指针,建议手动
ptr = NULL -
不能 free 栈、全局区、常量区地址,只能释放堆指针
-
三、动态内存常见问题
1. 内存泄漏
成因:堆空间使用完毕,没有调用 free 释放,程序运行期间持续占用内存。 触发场景:函数内 malloc,函数结束前没有 free;指针覆盖丢失堆地址。 规避:每一次 malloc/calloc/realloc,配套对应 free。
2. 野指针
两种情况:
-
指针未初始化,随机指向未知内存
-
堆内存 free 之后,没有将指针置空,继续解引用
*p操作 解决方案:指针定义初始化为NULL;free 后立刻p = NULL。
3. 越界访问
访问范围超过 malloc 申请的字节长度,修改堆相邻内存,造成程序崩溃、逻辑错乱。
4. 重复释放
对同一个堆地址多次调用 free,操作系统报双重释放错误,程序直接终止。
四、堆区使用典型案例
案例 1:动态 int 数组
#include <stdio.h>
#include <stdlib.h> int main(void)
{ int len = 5; int *p = malloc(len * sizeof(int));
if(p == NULL) { perror("malloc"); return -1; } // 赋值
for(int i = 0; i < len; i++) p[i] = i; // 扩容到10个元素
int *tmp = realloc(p, 10 * sizeof(int)); if(tmp != NULL) p = tmp; // 打印
for(int i = 0; i < 10; i++)
printf("%d ", p[i]); free(p);
p = NULL; // 置空防野指针
return 0; }
案例 2:动态结构体
#include <stdio.h>
#include <stdlib.h> #include <string.h>
struct Student { char name[32]; int age; int score; };
int main(void) { struct Student *stu = malloc(sizeof(struct Student));
if(stu == NULL) return -1; strcpy(stu->name, "ZhangSan");
stu->age = 18; stu->score = 95;
printf("%s %d %d\n", stu->name, stu->age, stu->score);
free(stu);
stu = NULL; return 0; }
五、补充:变量生命周期与存储类型关联
-
局部 auto 变量:存栈区,函数结束销毁,生命周期仅限当前代码块
-
static 静态变量:存.data/.bss 数据区,程序运行全程存在,仅初始化一次
-
全局变量:存数据区,程序启动开辟、程序结束释放,全文件可访问
-
堆动态内存:手动 malloc 创建、free 销毁,生命周期完全由程序员控制
-
register 寄存器变量:优先存 CPU 寄存器,无内存地址,无法取
&;仅编译器建议,不强制
六、配套编译四步骤(内存分区前置知识点)
gcc 编译 C 程序分为 4 阶段,内存分区在链接阶段完成布局:
-
预处理
gcc -E main.c -o main.i:展开宏、头文件、条件编译 -
编译
gcc -S main.i -o main.s:C 代码转汇编指令 -
汇编
gcc -c main.s -o main.o:汇编转二进制目标文件 -
链接
gcc main.o -o a.out:整合库函数,划分内存五大分区,生成可执行文件 a.out 可执行文件存硬盘,运行时操作系统加载到内存各分区执行。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)