我最近遇到了一篇文章“Deep C Secrets”,其中讨论了编译器在编译时解析变量。这对于全局变量和静态变量是可能的,因为它们在程序结束之前占用空间,但是局部变量在堆栈上获得空间的情况是什么?他们是否在运行时分配了空间,如果是,编译器如何跟踪他们的地址?
3 回答
好吧,本地变量的工作方式与全局/静态变量不同。
本地变量在堆栈上“分配”,而堆栈又是系统为正在运行的程序分配的一块内存。CPU持有一个指向该堆栈的“指针”,称为堆栈指针,并且一些编译器/CPU“魔术”会在函数调用上更新该指针。
最后,“堆栈指针”有点指向每个函数调用的本地内存块,就像一张对每个函数调用都是私有的纸,因此函数可以使用它来记录在其他地方不可见的笔记。因此,编译器并没有真正处理局部变量的“地址”,因为它们是在运行时确定的——相反,编译器“跟踪”该“纸”上局部变量的位置。换句话说,局部变量的位置是“相对于堆栈指针”,或者保留为“堆栈指针的偏移量”
它们的地址是相对于堆栈指针(在大多数情况下实际上是基指针,IIRC),每次执行函数调用时都会移动(inline
当然函数除外)。
要读取局部变量,生成的程序集将如下所示:
mov eax, [ebp - 4]
其中-4
是变量相对于ebp
(扩展(32 位)基本指针)的位置。如果您有两个int
变量(因此是两个 32 位变量),它们的位置将是-8
和-4
(因为在 X86 架构上 32 位是 4 字节宽)。
如有疑问,最好的办法是编写一个简短的程序,并将其编译为汇编:
gcc -S program.c -o output.S
cat output.S
有关汇编级别的函数调用的更多信息,请参阅此 wikibook。
你的问题很有趣。我在这里泛泛而谈,但只要我记得,这就是事实。秘密在于编译器在编译时强制所有调用例程和所有被调用例程遵循相同的协议。这就是使这一切正常工作的原因。
编译器输出汇编代码,以便调用例程将函数参数推入堆栈(可能按它们列出的顺序)。然后调用例程跳转到被调用例程,将返回地址(函数调用之后的语句)留在堆栈上。
编译器为被调用的例程输出类似的汇编代码,以便按照它们被推送的相同顺序在堆栈上查找这些参数。当被调用的例程使用完参数时,它会将它们从堆栈中弹出;从堆栈中弹出返回地址(保留它);将答案(即函数返回类型)压入堆栈(调用例程将在其中查找),然后跳转到返回地址。
调用例程从堆栈中弹出答案并开始使用它。
因此,这些函数参数的整个过程是可重定位的。
顺便说一句,所有“本地”变量(在被调用函数内)也存在于堆栈中,但仅在被调用函数运行时存在。被调用函数临时借用该空间(直到它返回)。只有函数知道那些局部变量在哪里。这就是函数返回后局部变量丢失的原因。
希望我一切顺利。