进程运行时,地址空间通常划分为代码段、数据段(含已初始化与未初始化全局区)、堆与栈等区域。理解各段的职责,有助于分析变量存放位置、段错误与堆内存泄漏等问题。
栈(stack)随函数调用由系统自动使用:每次调用在栈上压入一个栈帧,存放局部变量、参数与返回地址等;函数返回后该栈帧被回收,其中的局部变量随之失效。栈空间通常较小,递归过深或局部数据过大可能导致栈溢出。
堆(heap)供程序在运行时按需申请较大、生命周期更长的内存,例如通过 malloc 分配(详见动态内存分配)。堆上的块不会随函数返回自动释放,须配对调用 free,否则可能造成泄漏;堆与栈一般从地址空间两端向中间扩展,中间剩余部分为自由空间。
典型布局概览
下图自低地址到高地址列出常见区域:最下方为只读的代码段(.text);其上是数据段在实现上的两块——Data(已初始化全局/static)与 BSS(未初始化全局/static,启动时清零);再往上是向高地址增长的堆、中间自由空间,以及向低地址增长的栈。不必在图中再画一层数据段外框,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。
本节说明了栈、堆与静态数据段的分工。下一节介绍文件输入输出,将数据持久化到磁盘。