(假设 x86)
首先您必须了解堆栈。
函数使用称为“堆栈”的内存区域。你可以把它想象成一堆盘子,每个盘子都包含一个 DWORD(32 位)数据。 CPU 中有一个寄存器来跟踪我们正在处理的堆栈中的当前位置(它只是一个虚拟内存地址)。它称为堆栈指针,通常存储在 esp 寄存器中。
当函数与堆栈交互时,它们通常会执行以下两种操作之一:push 或 pop。 “推”是指将某些内容放在堆栈顶部,包括将堆栈指针移动到下一个最高位置,然后将某些内容复制到该新位置(新顶部)。推送“增加了堆栈”,因为现在存储了更多数据(更多板)。
“pop”是指堆栈上最顶层的项目被“移除”,这包括将当前位于堆栈顶部的任何内容(由 esp 寄存器指向)复制到 cpu 寄存器(通常是 eax)然后将堆栈指针移动到堆栈中较低的一个位置。
所以现在我们可以谈谈设置调用函数了。
代码
t.B(3, 4);
组装
// here is a push we described above. The function we are in currently is
// pushing the value "4" onto the stack. This is one of the arguments to the
// B function we are calling. Note that we push the last argument first
push 4
// here is another push. This time we are pushing the next argument to the
// B function
push 3
call B // this call sets up the context for the next function to run
当 调用 发生时,我们正在将上下文从当前函数转换到被调用的函数。函数需要运行的额外信息是参数,我们将其压入堆栈。
新函数现在将对堆栈进行一些内部管理,为它拥有的局部变量腾出空间,并将堆栈指针保存到寄存器中,以便在函数返回时可以恢复它。如果这没有发生,那么调用函数在重新获得控制权时就会完全迷失方向,不知道如何访问它之前放入堆栈的内容,例如它自己的局部变量或堆栈指针的上下文调用它的函数。
现在它正在组装中发生(从 Havenard 那里偷来的)。
// Here is the B function making sure that the calling function can get back to
// the it's stack context when B returns.
push ebp
mov ebp, esp
// remember when I said that a push was growing the stack. Well you can also grow
// it just by moving the stack pointer higher, as if there were already more plates there
// you may wonder why we are subtracting (sub) from the stack pointer (esp) to grow it
// the reason is that the stack "grows down" in memory. In other words, as the stack grows
// the memory addresses of the stack grow smaller.
// the reason we are subtracting 4 is because we only need to grow the stack by one plate
// so that we can store the local variable 'result' there. If we had 2 local variables
// we would have subtracted 8
sub esp, 4
// the instructions below are simply moving the static value 1 into the local variable
// 'result'. Local variables are always referenced relative to the bottom of the stack
// context for the current function. This value is stored in the ebp register, which we
// saw earlier in the function setup above.
// so now we think of the location where the 'result' variable is stored as "ebp-4"
// we know that because we put it there.
mov dword ptr [ebp-4], 1 // result = 1 (true)
// eax is a special register that contains the return value of the function. That is why
// you see the value of 'result' (which we know as [ebp-4] in the eax register
mov eax, dword ptr [ebp-4]
// We adjust the stack pointer back to it's previous location
// before we subtracted to make room for our local variable
add esp, 4
// Our work is done now.. time to clean stuff up for our calling function and
// leave things as we found them. Our trusty ebp register stores the old stack pointer
// that our calling function needs to resume it's stack context.
mov esp, ebp
pop ebp
ret
我确定我遗漏了一些细节,尤其是从 B 函数返回时,但我认为这是一个很好的概述。