如何确定变量是函数的局部变量还是传递给函数的参数?
确定变量是局部变量还是传递给函数的参数
参数
它不仅取决于平台,而且不同的函数具有不同的调用约定。调用约定基本上告诉您如何知道参数在哪里。它没有说明局部函数堆栈框架布局。
理解当一个函数或方法可以被编译器或链接器证明不能被编译器在当前翻译单元或当前二进制文件中的链接器中可以看到的之外的代码访问时,他们可以自由地做这件事也非常重要他们想要什么。这包括以与ABI不对应的方式传递参数。随着链接时间代码生成的日益普及,这是一个更大的问题。
ABI 基本上是平台上所有二进制文件承诺遵守的接口,以便可以保证用不同语言编写的二进制文件可以交互。但是,如果您的二进制文件不导出函数,则它通常不需要遵守 ABI。
变量
变量实际上只能存储在三个位置
它们可以存储在当前函数的本地堆栈帧中的堆栈中。当相对于堆栈指针进行访问并且偏移量在函数堆栈帧内并且早于保存的本地寄存器时,可以看到这一点。这通常意味着该变量在没有链接寄存器的情况下调用约定的返回地址之前。偏移量在返回地址之前的访问通常是参数。使用链接寄存器的调用约定不会在堆栈上存储返回地址,除非它们必须使变量早于堆栈帧的保存寄存器部分,并且基于堆栈的参数在堆栈帧的保存寄存器部分之前。
全局存储,通常在堆或加载器映射的可执行二进制文件的一段中。
寄存器,这通常用于函数中经常使用的变量,因为寄存器组是 CPU 可用的最快的存储。如果编译器确定 CPU 有足够的寄存器来存储寄存器组中的所有变量,则变量也可以在函数运行期间保存在寄存器中。哪些寄存器存储什么以及何时存储的优化称为寄存器分配。意识到超出范围或稍后在函数中未使用的变量会释放寄存器也很重要。这意味着一个寄存器可以在函数的不同阶段映射到不同的变量。
调用约定
IA32 调用约定
按可能遇到的粗略顺序。
cdecl
在 cdecl 中,子程序参数在堆栈上传递。整数值和内存地址在 EAX 寄存器中返回,浮点值在 ST0 x87 寄存器中返回。寄存器 EAX、ECX 和 EDX 是调用者保存的,其余的都是被调用者保存的。调用新函数时,x87 浮点寄存器 ST0 到 ST7 必须为空(弹出或释放),退出函数时 ST1 到 ST7 必须为空。
这个电话
在 Microsoft Visual C++ 编译器上,this 指针在 ECX 中传递,它是清理堆栈的被调用者,反映了 C 中用于此编译器和 Windows API 函数的 stdcall 约定。当函数使用可变数量的参数时,调用者会清理堆栈。
标准调用
Callee 负责清理堆栈,但参数按从右到左的顺序压入堆栈,如 _cdecl 调用约定。寄存器 EAX、ECX 和 EDX 被指定在函数内使用。返回值存储在 EAX 寄存器中
快速通话
传递适合 ECX 和 EDX 的前两个参数(从左到右计算)。剩余的参数从右到左压入堆栈。
帕斯卡
参数按从左到右的顺序压入堆栈(与cdecl相反),被调用者负责在返回前平衡堆栈。
AMD64 调用约定
微软
Microsoft x64 调用约定[9](用于 x86-64 上的长模式)使用寄存器 RCX、RDX、R8、R9 用于前四个整数或指针参数(按从左到右的顺序)和 XMM0、XMM1、 XMM2、XMM3 用于浮点参数。额外的参数被压入堆栈(从右到左)。如果 64 位或更少,则在 RAX 中返回整数返回值(类似于 x86)。浮点返回值在 XMM0 中返回。长度小于 64 位的参数不进行零扩展;高位包含垃圾。
几乎所有人
紧随其后的是 Solaris、GNU/Linux、FreeBSD 和其他非 Microsoft 操作系统。前六个整数或指针参数在寄存器 RDI、RSI、RDX、RCX、R8 和 R9 中传递,而 XMM0、XMM1、XMM2、XMM3、XMM4、XMM5、XMM6 和 XMM7 用于浮点参数。对于系统调用,使用 R10 代替 RCX。[11] 与 Microsoft x64 调用约定一样,附加参数在堆栈上传递,返回值存储在 RAX 中。
ARM 调用约定
r14 是链接寄存器,r12 是过程内调用暂存寄存器,r0 到 r3 用于保存传递给子程序的参数值,也保存从子程序返回的结果。
超过 4 个参数,它们会被压入堆栈。
PowerPC 调用约定
由于 PowerPC 有如此多的 GPR(32 个与 ia32 的 8 个相比),参数在以 gpr3 开头的寄存器中传递。寄存器 gpr3 到 gpr12 是易失性(调用者保存)寄存器,(如有必要)必须在调用子程序之前保存并在返回后恢复。可变参数计数函数将参数存储在被调用者的堆栈中。
MIPS 调用约定
O32
寄存器 $a0-$a3 中函数的前四个参数;随后的参数在堆栈上传递。堆栈上的空间是为 $a0-$a3 保留的,以防被调用者需要保存其参数,但调用者不会将寄存器存储在那里。返回值存储在寄存器 $v0 中;第二个返回值可以存储在 $v1 中。
N32 和 N64
将前八个参数传递给寄存器 $a0-$a7 中的函数;随后的参数在堆栈上传递。返回值(或指向它的指针)存储在寄存器 $v0 中;第二个返回值可以存储在 $v1 中。在 N32 和 N64 ABI 中,所有寄存器都被认为是 64 位宽。
虽然这个答案肯定不是在所有情况下都是正确的,但您的老师可能正在寻找的答案是:
- 局部变量的形式为
[EBP - ...]
- 传递的参数采用以下形式
[EBP + ...]
我不确定你所说的变量是什么意思,我只是假设它是函数体执行过程中某个时刻的寄存器或堆栈地址的值。
如果它是通过参数传递的,它的值是由调用者通过堆栈或寄存器定义的(在此处查看不同的调用约定)。如果不是,那么它是由函数体本身定义的。