实用科技屋
霓虹主题四 · 更硬核的阅读氛围

函数调用时,局部变量是怎么被“悄悄”传走的?

发布时间:2026-01-24 07:00:29 阅读:140 次

写 C 或 C++ 时,你有没有好奇过:每次调用函数,形参怎么就自动拿到实参的值了?局部变量又为啥一退出函数就“消失”了?背后没玄机,全是栈在干活。

栈不是堆,是后进先出的“快递柜”

想象你家楼下的智能快递柜——最上面那格放的总是最后塞进去的包裹。栈也一样:函数一进来,系统就在栈顶划一块地,把参数、返回地址、局部变量全压进去;函数一结束,“啪”一声弹出整块区域,连带所有变量一起清空。干净利落,不拖泥带水。

一个真实的小例子

看这段代码:

void calc(int a, int b) {
int sum = a + b;
int flag = 1;
printf("sum=%d, flag=%d\n", sum, flag);
}

int main() {
calc(3, 5);
return 0;
}

calc(3, 5) 被调用时,栈上大概长这样(从高地址到低地址):

  • 返回地址(main 中下一条指令的位置)
  • 实参 b=5
  • 实参 a=3
  • 局部变量 sum(刚算出是 8)
  • 局部变量 flag=1

注意:参数和局部变量都在同一块栈帧里,只是顺序和生命周期不同。参数在前,是“别人给的”;局部变量在后,是“自己生的”。但它们都靠栈指针(SP)和帧指针(FP)精准定位,谁也不越界。

为什么不能返回局部变量的地址?

新手常踩这个坑:

int* bad_func() {
int x = 42;
return &x; // 危险!
}

函数返回后,这块栈内存立刻被标记为“可回收”。下次调用别的函数,可能就直接覆盖掉 x 原来的位置。你拿着那个地址去读,读到的可能是垃圾数据,也可能是刚写进去的另一个变量——结果飘忽不定,调试起来头发掉一地。

栈传递,其实不传“值”本身?

严格说,栈上传的不是“变量”,而是“值的副本”。比如传一个结构体:

struct Point { int x; int y; };
void move(struct Point p) { ... }
// 调用 move({10, 20});
// 整个 struct 的 8 字节(假设 int 是 4 字节)会被原样拷贝进栈

所以改 p.x 不会影响原来的结构体——因为压根就没传引用,更没传指针,就是完完整整搬了一份过去。这也是为啥大结构体传参慢,编译器有时会悄悄改成传指针,但那是优化层面的事了,语义上你写的还是“值传递”。

搞懂栈怎么托住局部变量,debug 时看汇编、查栈帧、分析段错误,心里就有底了。它不炫技,但天天在你代码底下稳稳托着——就像自行车链条,看不见,一断就蹬不动。