变量作用域切换的本质是栈帧的压入与弹出:进入代码块即压栈,退出即弹出;查找标识符时从栈顶逐层向下回溯,直至全局帧;重声明检查限于当前栈帧,生命周期由栈帧存续决定。

变量作用域切换的本质,是程序执行路径在嵌套结构中进出的过程,而栈结构正是对这一过程最自然的建模方式——压入对应“进入”,弹出对应“退出”,查找对应“就近回溯”。
作用域切换 = 栈帧进出
每次遇到代码块边界(如 { 或函数入口),编译器就压入一个新栈帧;每次遇到匹配的 } 或函数返回,就弹出当前栈帧。这个动作不依赖语法糖,而是直接映射到 AST 中的 BlockStatement 节点起止位置。
- 全局作用域作为栈底,始终存在
- 函数体、if 分支、for 循环体、宏展开体等,只要构成独立声明空间,就触发一次压栈
- 栈顶帧即“当前作用域”,所有新声明的变量只写入该帧的符号表,不会污染上层
变量查找 = 自顶向下的线性扫描
当遇到一个标识符(比如 x),编译器不做跨层合并,也不预计算链路,而是从栈顶开始逐层向下查:
- 先查当前帧:命中则立即使用,停止搜索
- 未命中则跳转父帧(即栈中下一层),继续查找
- 直到栈底(全局帧)仍未找到,报 “undefined identifier” 错误
这种机制天然保障“内层遮蔽外层”,无需额外标记或作用域 ID。
重声明检查 = 仅限当前帧内比对
是否允许重复定义,判断范围严格限定在当前栈顶帧内部:
- int x; 和 char x; 在同一块里连续出现 → 编译错误
- int x; 在外层块,float x; 在内层 { } 里 → 合法,因属不同帧
- 函数参数与函数体内 let 声明在同一帧 → 不可同名
生命周期终结 = 栈帧自动释放
变量的“存活时间”不由语义规则硬编码,而是由其所处栈帧的存续决定:
- 帧弹出时,其附带的符号表整张丢弃,所有条目失效
- 局部变量的内存分配(如栈上偏移)也随帧销毁而释放
- 静态变量(static)例外:它不绑定栈帧,而是绑定编译单元,在数据段长期驻留
这解释了为什么 return 之后无法访问局部变量——不是被“清空”,而是承载它的整个上下文已从栈中移除。











