2

每当我读到 C 中的程序执行时,它都很少提及函数执行。我仍在试图找出当程序从另一个函数调用它到它返回的时间开始执行它时,函数会发生什么?函数参数如何存储在内存中?

4

2 回答 2

9

那是未指定的;这取决于实施。正如 Keith Thompson 所指出的,它甚至不必告诉您它是如何工作的。:)

一些实现会将所有参数放在堆栈上,一些会使用寄存器,并且许多使用混合(前n个参数传入寄存器,然后它们进入堆栈)。

但是函数本身只是代码,它是只读的,在执行过程中没有什么“发生”。

于 2012-08-31T08:16:28.007 回答
3

这个问题没有一个正确的答案,这在很大程度上取决于编译器编写者如何确定执行此操作的最佳模型。标准中有很多位描述了这个过程,但大部分是实现定义的。此外,该过程取决于系统架构、您的目标操作系统、优化级别等。

采取以下代码: -

int DoProduct (int a, int b, int c)
{
  return a * b * c;
}

int result = DoProduct (4, 5, 6);

MSVC2005 编译器使用标准调试构建选项为上述代码的最后一行创建了这个:-

push        6    
push        5    
push        4    
call        DoProduct (411186h) 
add         esp,0Ch 
mov         dword ptr [ebp-18h],eax 

在这里,参数被压入堆栈,从最后一个参数开始,然后是倒数第二个参数,依此类推,直到第一个参数被压入堆栈。调用该函数,然后从堆栈中删除参数(add esp,0ch),然后保存返回值 - 结果存储在 eax 寄存器中。

这是该功能的代码:-

push        ebp  
mov         ebp,esp 
sub         esp,0C0h 
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp-0C0h] 
mov         ecx,30h 
mov         eax,0CCCCCCCCh 
rep stos    dword ptr es:[edi] 
mov         eax,dword ptr [a] 
imul        eax,dword ptr [b] 
imul        eax,dword ptr [c] 
pop         edi  
pop         esi  
pop         ebx  
mov         esp,ebp 
pop         ebp  
ret              

该函数所做的第一件事是创建一个本地堆栈帧。这涉及在堆栈上创建一个空间来存储局部变量和临时变量。在这种情况下,保留了 192 (0xc0) 个字节(前三个指令)。它这么多的原因是允许编辑和继续功能有一些空间来放置新变量。

接下来的三个指令保存 MS 编译器定义的保留寄存器。然后刚刚创建的堆栈帧空间被初始化为包含一个特殊的调试签名,在本例中为 0xCC。这意味着unitialised memory,如果您在调试模式下看到一个仅包含 0xCC 的值,那么您就忘记了初始化该值(除非 0xCC 是该值)。

一旦所有的内务处理都完成了,接下来的三个指令实现函数的主体,两个相乘。之后,保留的寄存器被恢复,然后堆栈帧被销毁,最后函数以ret. 幸运的是,imul将乘法的结果放入eax寄存器中,因此没有特殊的代码可以将结果放入正确的寄存器中。

现在,您可能一直在想,那里有很多东西并不是真正需要的。你是对的,但调试是为了让代码正确,上面的很多内容都有助于实现这一点。在发布时,有很多可以摆脱的东西。不需要堆栈帧,因此不需要初始化它。没有必要保存保留的寄存器,因为它们没有被修改。事实上,编译器创建了这个:-

mov         eax,dword ptr [esp+4] 
imul        eax,dword ptr [esp+8] 
imul        eax,dword ptr [esp+0Ch] 
ret              

如果我让编译器来做,它会被内联到调用者中。

还有更多可能发生的事情:在寄存器中传递的值等等。此外,我还没有深入了解浮点值和结构/类如何传递给函数和从函数传递。还有更多我可能遗漏的内容。

于 2012-08-31T08:39:17.700 回答