进程运行时,地址空间通常划分为代码段、数据段(含已初始化与未初始化全局区)、堆与栈等区域。理解各段的职责,有助于分析变量存放位置、段错误与堆内存泄漏等问题。

栈(stack)随函数调用由系统自动使用:每次调用在栈上压入一个栈帧,存放局部变量、参数与返回地址等;函数返回后该栈帧被回收,其中的局部变量随之失效。栈空间通常较小,递归过深或局部数据过大可能导致栈溢出。

堆(heap)供程序在运行时按需申请较大、生命周期更长的内存,例如通过 malloc 分配(详见动态内存分配)。堆上的块不会随函数返回自动释放,须配对调用 free,否则可能造成泄漏;堆与栈一般从地址空间两端向中间扩展,中间剩余部分为自由空间。

典型布局概览

下图自低地址到高地址列出常见区域:最下方为只读的代码段(.text);其上是数据段在实现上的两块——Data(已初始化全局/static)与 BSS(未初始化全局/static,启动时清零);再往上是向高地址增长的堆、中间自由空间,以及向低地址增长的栈。不必在图中再画一层数据段外框,Data 与 BSS 即数据段在实现上的常见划分。

高地址 栈(Stack) 局部变量 · 函数参数 · 返回地址 向下增长 自由空间 堆(Heap) malloc 动态分配 向上增长 BSS(未初始化全局) Data(已初始化全局) 代码段 .Text(只读) 低地址
低地址:代码段(只读)→ Data → BSS(合称数据段)→ 堆 ↑ / 栈 ↓,中间为自由空间。

1. 代码段(Text Segment)

存放程序的机器指令,通常只读,位于较低地址。占用大小取决于代码规模。

2. 数据段(Data Segment)

存放具有静态存储期的全局对象与 static 变量,可再分为:

  • 已初始化数据(Data):程序员给出初值的全局或 static 变量。
  • BSS:未显式初始化的全局或 static 变量,启动时由实现清零,故全局变量默认值为 0。
int g1 = 10;         /* 已初始化 → Data 段 */
char msg[] = "hi";   /* 已初始化 → Data 段 */
int g2;              /* 未初始化 → BSS 段 */
static int s1;       /* 未初始化 static → BSS 段 */
static int s2 = 20;  /* 已初始化 static → Data 段 */

3. 堆(Heap Segment)

供 malloc、realloc、free 等动态分配使用,自低地址向高地址增长。堆上内存不会随作用域结束自动释放,须配对释放,否则可能产生内存泄漏。

4. 栈(Stack Segment)

存放自动变量、函数参数与返回地址。每次函数调用形成栈帧,返回时销毁。栈自高地址向低地址增长;若与堆扩展相遇,可用空间耗尽,可能引发栈溢出。

void func(void) {
    int local_var = 10;   /* 典型位于栈上 */
}

打印地址以对照布局

下列程序打印若干对象的地址,便于与上图对照(具体数值因平台而异):

#include <stdio.h>
#include <stdlib.h>

int gvar = 66;
const int cgvar = 1010;
int ugvar;

void foo(void) {
    int lvar = 1;
    printf("lvar 的地址:%p\n", (void*)&lvar);
}

int main(void) {
    int *hvar = (int *)malloc(sizeof(int));

    printf("foo 的地址:    %p\n", (void*)&foo);
    printf("cgvar 的地址:  %p\n", (void*)&cgvar);
    printf("gvar 的地址:   %p\n", (void*)&gvar);
    printf("ugvar 的地址:  %p\n", (void*)&ugvar);
    printf("hvar 的地址:   %p\n", (void*)hvar);
    foo();
    free(hvar);
    return 0;
}

常见现象:代码与只读数据地址较低,已初始化全局数据略高,堆块地址往往更高,局部变量地址位于栈区(高地址一侧)。

用 size 命令查看段大小

GCC 的 size 可列出可执行文件中各段字节数:

size memory-layout
# text   data    bss    dec    hex
#  960    248      8   1216   4c0

增加未初始化全局变量时,bss 往往增大;若改为带初值的全局变量,相应字节可能从 bss 转入 data。

本节说明了栈、堆与静态数据段的分工。下一节介绍文件输入输出,将数据持久化到磁盘。