程序运行时,内存分成了好几个区。了解这些区域,写代码时心里就有张地图,变量放哪、为什么有的会段错误、哪些占多少内存,都有数。

四大段

高地址 栈(Stack) 局部变量 · 函数参数 · 返回地址 向下增长 自由空间 堆(Heap) malloc 动态分配 向上增长 BSS(未初始化全局) Data(已初始化全局) 代码段 .Text(只读) 低地址
栈向下长、堆向上长,中间是自由空间;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 涨上来,亲眼看到变量在段之间搬家,这课的印象就深了。内存布局弄清楚,段错误、内存泄漏两大顽疾至少能定位到哪个区出事。