程序运行时,内存分成了好几个区。了解这些区域,写代码时心里就有张地图,变量放哪、为什么有的会段错误、哪些占多少内存,都有数。
四大段
栈向下长、堆向上长,中间是自由空间;BSS/Data 放全局与 static 变量,代码段只读。
1. 代码段(Text Segment)
装程序的函数和指令,通常只读,防止运行中被改。靠下存放。占多少取决于指令数和程序复杂度,写的小程序这区最小。
2. 数据段(Data Segment)
装全局变量和 static 变量,整个运行期一直占着。又分两块:
- 已初始化段(Data):程序员显式给了初值的全局/static 变量。
- 未初始化段(BSS):没给初值的全局/static 变量,系统会在运行时自动清零。所以全局变量默认是 0。
int g1 = 10; /* 已初始化 → Data 段 */
char msg[] = "hi"; /* 已初始化 → Data 段 */
int g2; /* 未初始化 → BSS 段,运行时清 0 */
static int s1; /* 未初始化 static → BSS 段 */
static int s2 = 20; /* 已初始化 static → Data 段 */3. 堆(Heap Segment)
动态分配的地盘,从 BSS 尾部往高地址长。malloc / realloc / free 都在这里操作,进程里所有动态加载的共享库共用它。堆用得不会自动回收,所以要用完就 free,否则会内存泄漏。
4. 栈(Stack Segment)
装局部变量、函数参数、返回地址。每次函数调用创建一个栈帧,返回时销毁。站得高、往下长,和堆相向而行;两者一碰头,空闲内存就没了。
void func() {
int local_var = 10; /* 局部变量 → 栈 */
}递归太深把栈耗光(栈溢出),就是这个区撑爆了。
用数组把地址对照着打出来
实践是最直观的。下面这段打印程序里几类东西的地址,能看出它们分布的规律:
#include <stdio.h>
#include <stdlib.h>
int gvar = 66; /* 已初始化全局 → Data */
const int cgvar = 1010; /* 只读常量,往往也放只读区 */
int ugvar; /* 未初始化全局 → BSS */
void foo() {
int lvar = 1; /* 局部变量 → 栈 */
printf("lvar 的地址:%p\n", (void*)&lvar);
}
int main() {
int *hvar = (int *)malloc(sizeof(int)); /* → 堆 */
printf("foo 的地址: %p\n", (void*)&foo); /* 代码段 */
printf("cgvar 的地址: %p\n", (void*)&cgvar); /* 只读常量区 */
printf("gvar 的地址: %p\n", (void*)&gvar); /* Data */
printf("ugvar 的地址: %p\n", (void*)&ugvar); /* BSS */
printf("hvar 的地址: %p\n", (void*)hvar); /* 堆 */
foo(); /* 栈 */
free(hvar);
return 0;
}典型输出里就能看到:代码地址(0x40…)低、数据地址(0x40…)稍高、堆地址(0x11…)更高、栈地址(0x7ffe…)在最高处,和上面的布局图对得上。
用 size 命令验证段大小
GCC 的 size 命令能打印二进制里各段字节数。一个空 main 程序:
size memory-layout
# text data bss dec hex
# 960 248 8 1216 4c0加一个未初始化全局变量,bss 会涨:
# text data bss
# 960 248 12把它改成已初始化,bss 掉回去、data 涨上来,亲眼看到变量在段之间搬家,这课的印象就深了。内存布局弄清楚,段错误、内存泄漏两大顽疾至少能定位到哪个区出事。