09、变量和存储区介绍
“这个变量放在栈上还是堆上”很常见,但在回答之前,应先分清变量的作用域、对象的存储期与具体平台的内存布局。把这三件事混成一张固定地址图,容易产生看似直观、实际不准确的结论。
先分清名字与对象
本文以 C17 为基线。变量声明引入名字;对象占据存储。作用域决定名字在哪段代码中可见,存储期决定相应存储持续多久,两者不是同一个概念。
例如函数里的 static 局部变量,只能在相应作用域中通过该名字访问,却不会随着函数返回而销毁。文件作用域的变量也不是“任何文件都能无条件访问”:跨文件使用还涉及声明与链接属性。
三种常见存储期
- 静态存储期:如文件作用域对象、静态局部对象,存储贯穿程序执行。
- 自动存储期:如普通块内局部对象,通常随进入和离开所在代码块开始、结束生命周期。
- 分配存储期:通过
malloc等取得的存储,成功分配后持续到free释放,不由某个局部指针的作用域自动管理。
C17 还有线程存储期,用 _Thread_local 声明,本文不把它误归入普通全局变量。本节先聚焦前三种最常遇到的情况。

指针与它指向的对象可能活得不一样长
int* p = malloc(...) 中,p 本身是一个指针对象;分配得到的那块存储是另一个对象所在的位置。p 可以是自动局部变量,但离开 p 的作用域不会自动调用 free。
反过来,释放那块存储后,p 可能还在作用域内,却已经不能再用于访问原对象。只看指针变量是否还存在,无法判断所指对象是否仍然有效。
C17 可运行示例
#include <stdio.h>
#include <stdlib.h>
int global_value;
static int file_value = 2;
void visit(void) {
int local_value = 7;
static int calls;
++calls;
printf("%d %d\n", local_value, calls);
}
int main(void) {
printf("%d %d\n", global_value, file_value);
visit();
visit();
int* p = malloc(sizeof *p);
if (p == NULL) return 1;
*p = 42;
printf("%d\n", *p);
free(p);
p = NULL;
return 0;
}
输出依次是 0 2、7 1、7 2、42。没有显式初始化的静态存储期对象会零初始化;不能因此推断普通未初始化局部 int 也会自动变成 0。
代码段、数据段、堆与栈怎样理解
在常见 Linux ELF 实现中,可以用代码区、只读数据、已初始化数据、BSS、堆及线程栈等帮助理解程序。但这是平台与实现层面的模型,不是 C 标准规定的通用地址排列。
编译器可能把局部值放入寄存器或直接优化掉;堆和栈不保证相邻,也不保证所有平台都按某个固定方向增长。字符串字面量不能修改,而存放其地址的指针对象位于哪里,要看该指针自己的声明与存储期。
初始化与赋值不是同一步
int value = 10; 是定义时初始化,随后 value = 20; 是赋值。文件作用域的 int value; 没有显式写初值,但其静态对象仍被零初始化;不能称它是“值随机的未初始化全局变量”。
C 与 C++ 的初始化、对象构造和链接规则有差异。例如 C++ 的 new/delete 不只是 C 的 malloc/free 换个写法;学习本章时不要把两套规则混为一谈。
练习与依据
试着分别说出示例中 global_value、file_value、local_value、calls、p 及分配对象的作用域和存储期。再回答:如果忘记 free,指针离开作用域后为什么仍然会泄漏?
示例按 C17 编译。标准版本与可用草案见 WG14 项目列表;基础条款也可对照公开的 C11 草案 N1570,注意它不是 C17 正式标准。Linux ELF 文档用于了解平台实现,不把标准概念与 ELF 布局混为一谈。
