一个典型的C程序在内存中(特指虚拟内存空间)会被划分为以下几个关键区域,从低地址到高地址大致如下:完整分为:代码段(.text)、只读常量段(.rodata)、全局数据段(.data/.bss)、堆(heap)、栈(stack)。
注意,一个进程只对应一套五大内存分区。函数再多、嵌套再深,只是在唯一的栈里折腾,不会多开一套内存分区。五大内存分区结构体如下图所示:
在我们实际代码开发中,其实是很难把上面的所有概念和写的代码联系起来,本文将通过最简单的示例代码来说明内存的五大分区。
#include<stdio.h>#include<stdlib.h>// 1. 全局初始化变量 → .data 全局数据段int g_init = 10;// 2. 全局未初始化变量 → .bss 全局零初始化段int g_uninit;// 3. 字符串字面量 → .rodata 只读常量段const char* g_str = "hello world";voidfunc(){ // 4. 普通局部变量 → 栈 stack int local_a = 20; // 5. static局部变量(初始化)→ .data static int s_init = 5; // 6. static局部变量(未初始化)→ .bss static int s_uninit; // 7. malloc分配内存 → 堆 heap int* p_heap = malloc(sizeof(int)); *p_heap = 99; return;}intmain(){ func(); return 0;}
代码段 .text
专门存放程序编译后所有的二进制机器指令,不存任何变量数据。上面代码里的 main函数、func函数的所有执行逻辑,比如变量赋值、函数调用、malloc申请内存、return 返回等代码,编译后全部变成机器指令存放在代码段。本段权限只读、可执行,程序运行过程中绝对不会被修改,生命周期跟随整个进程,程序启动加载、程序结束才释放。无论程序有多少个函数、多少层嵌套,所有执行指令全部统一在这一个代码段内,不会新开空间。
只读常量段 .rodata
专门存放程序中固定不变的只读内容,核心就是字符串字面量和全局const常量。对应案例中 "hello world" 这个字符串本体就存放在当前段,而指针变量 g_str 只是存储了这个字符串的内存地址。本段内存权限严格只读,代码里如果强行修改字符串内容,会直接触发段错误、程序崩溃。数据生命周期全程常驻内存,不会随函数调用结束而消失。

全局数据区(.data + .bss 统一区域)
所有全局变量、static静态变量,不管是全局还是函数内部定义的,全部统一存放在全局数据区,不在栈上、函数结束不会销毁,全程常驻进程内存。其中 .data 段存放已经初始化的全局和静态变量,对应案例里的 int g_init = 10、static int s_init = 5,程序加载时就会把初始值写入内存,占用程序文件体积。 而 .bss 段存放只声明、未初始化的全局和静态变量,对应案例里的 int g_uninit、static int s_uninit,本段特点是不占用程序文件大小,程序启动后系统会统一自动清零,是面试高频考点。
static 和 const 有啥区别?const:值禁止修改 → 进只读区static:生命周期变长、值可以随便改 → 进全局数据区
栈 stack
栈是程序运行最频繁的动态内存区域,专门存放普通局部变量、函数参数、函数返回地址、栈帧信息。案例中 func 函数内部的 int local_a = 20、指针变量 int* p_heap 都是典型的栈变量。栈完全由系统硬件自动管理,函数调用时自动开辟栈帧分配内存,函数执行结束立刻自动销毁回收,完全不用手动操作。栈空间很小,默认只有几MB,递归过深、定义超大局部数组都会造成栈溢出。每一次函数调用都是在当前进程唯一的栈空间内压栈、出栈,不会单独新开内存分区。
堆 heap
堆是唯一需要程序员手动管理的内存区域,专门存放 malloc、calloc、realloc 动态申请的内存。案例中 malloc(sizeof(int)) 开辟的内存、赋值的数值 99 全部存放在堆空间,而保存地址的指针变量 p_heap 本身在栈上。堆的生命周期完全由开发者掌控,malloc 申请后必须手动 free 释放,只申请不释放会造成内存泄漏,重复释放会导致程序崩溃。堆空间容量极大,适合存放大数据,进程运行期间不会自动回收,只有手动释放或程序结束系统才会收回内存。
分区 | 存放内容 | 生命周期 | 是否手动管理 |
.text 代码段 | 所有函数二进制指令 | 程序运行全程 | 不用管 |
.rodata 只读段 | 字符串常量、const全局常量 | 程序运行全程,只读不可改 | 不用管 |
.data 全局数据段 | 已初始化全局/static静态变量 | 程序运行全程 | 不用管 |
.bss 零段 | 未初始化全局/static静态变量 | 程序运行全程,启动自动清零 | 不用管 |
stack 栈 | 普通局部变量、函数参数、返回地址 | 当前函数内有效,退出自动销毁 | 硬件自动管理 |
heap 堆 | malloc 动态申请的内存 | malloc → free之间有效 | 程序员手动管理 |
高频易错点总结:
普通局部变量 → 栈;static局部变量 → 全局数据段,二者完全分开;
指针变量本身永远在栈/全局区;指针指向的数据,看来源:字符串常量在.rodata,malloc数据在堆;
栈自动释放,堆必须手动free,全局数据段程序结束才释放;
代码段只存指令,不存任何变量。