0

我想问函数签名、调用和定义的顺序

比如,计算机看起来第一、第二和第三哪个

所以:

#include <iostream>
using namespace std;

void max(void);
void min(void);

int main() {

max();

min();

return;
}

void max() {

return;
}

void min() {

return;
}

所以这就是我的想法,

计算机将转到 main 并查看函数调用,然后它会查看

函数签名,最后看定义。

这是正确的?

感谢

4

2 回答 2

1

您可能知道,编译器将程序转换为机器代码(通过几个中间步骤)。main()以下是在 Windows 8 上以调试模式在 Visual Studio 2012 上编译时的机器代码反汇编:

int main() {
00C24400  push        ebp                         # Setup stack frame
00C24401  mov         ebp,esp  
00C24403  sub         esp,0C0h  
00C24409  push        ebx  
00C2440A  push        esi  
00C2440B  push        edi  
00C2440C  lea         edi,[ebp-0C0h]              # Fill with guard bytes
00C24412  mov         ecx,30h                     
00C24417  mov         eax,0CCCCCCCCh  
00C2441C  rep stos    dword ptr es:[edi]  

max();
00C2441E  call        max (0C21302h)              # Call max  

min();
00C24423  call        min (0C2126Ch)              # Call min

return 0;
00C24428  xor         eax,eax  
}
00C2442A  pop         edi                         # Restore stack frame    
00C2442B  pop         esi  
00C2442C  pop         ebx  
00C2442D  add         esp,0C0h  
00C24433  cmp         ebp,esp  
}
00C24435  call        __RTC_CheckEsp (0C212D5h)   # Check for memory corruption
00C2443A  mov         esp,ebp  
00C2443C  pop         ebp  
00C2443D  ret 

确切的细节会因编译器和操作系统而异。如果 min() 或 max() 有参数或返回值,它们将根据体系结构适当地传递。关键是编译器已经计算出参数和返回值是什么,并创建了只传递或接受它们的机器代码。

如果您希望帮助调试或进行低级调用,您可以了解更多详细信息,但请注意发出的机器代码可能是高度可变的。例如,这是在同一系统上以发布模式编译的相同代码(即优化):

return 0;
01151270  xor         eax,eax  
}
01151272  ret 

如您所见,它已检测到这一点min()并且max()什么都不做并完全删除它们。由于现在没有要设置和恢复的堆栈帧,那已经消失了,只剩下一条指令将 eax 设置为 0 然后返回(因为返回值在 eax 寄存器中)。

于 2012-10-01T02:12:43.860 回答
1

这是正确的?

不。

你需要了解函数声明和函数定义的区别,编译、链接和执行的区别,以及非虚函数和虚函数的区别。

函数声明
这是一个函数声明:void max(void);. 它不会告诉编译器有关函数的作用的任何信息。它的作用是告诉编译器如何调用函数以及如何解释结果。当编译器在编译某个函数的主体时,称之为函数A,编译器不需要知道其他函数是做什么的。它只需要知道如何处理函数 A 调用的函数。编译器可能会生成与您的 C++ 函数调用相对应的汇编代码或某种中间语言代码。或者它可能会拒绝您的 C++ 代码,因为您的代码没有意义。

确定您的代码是否有意义是这些函数声明的另一个关键目的。这在多个函数可以具有相同名称的 C++ 中尤为重要。如果编译器不知道这些函数,它如何知道要调用半打左右的函数中的max哪一个?当您的 C++ 代码调用某个函数时,编译器必须找到与这些函数声明之一的最佳匹配(可能涉及类型转换)。如果编译器根本找不到匹配项,或者它找到多个匹配项但无法将一个匹配项区分为最佳匹配项,则您的代码没有意义。

当编译器确实找到最佳匹配时,生成的代码将以调用该函数的未定义外部引用的形式出现。该函数所在的位置不是编译器的工作。

函数定义
void max(void)是一个函数声明。对应void max() {...}的是该函数的定义。当编译器正在处理void max() {...}它时,它不必担心其他函数调用了它。它只需要担心处理void max() {...}。这个函数的主体变成了汇编或中间语言代码,插入到某个编译的目标文件中。编译器将这个生成代码的入口点的地址标记为这样。

编译与链接
到目前为止,我已经讨论了编译器的作用。它会生成与您的 C++ 代码相对应的低级代码块。由于这些外部引用,生成的代码还没有准备好迎接黄金时段。解决那些未定义的外部引用是链接器的工作。链接器是从多个目标文件、多个库构建可执行文件的工具。它跟踪将这些代码块放在可执行文件中的位置。那些未定义的外部引用呢?如果链接器已经将该引用放置在可执行文件中,则链接器只需填写该引用的占位符。如果链接器没有遇到该引用的定义,它会将引用和占位符放在仍然未解析的引用列表中。每次链接器向可执行文件添加一段代码时,它检查该列表以查看它是否可以修复任何那些仍未解决的引用。最后,您要么解决了所有参考文献,要么仍然有一些未完成的参考文献。后者是一个错误。前者意味着你有一个可执行文件。

执行
当你的代码运行时,这些函数调用实际上只是一些堆栈管理,它包裹在机器语言中,相当于那个邪恶的goto语句。无需检查您的函数声明;这些甚至在代码执行时都不存在。返回?那也是一个goto

非虚拟与虚拟功能
我上面所说的与非虚拟功能有关。虚拟功能确实发生了运行时调度。运行时分派与检查函数声明无关。这些虚拟功能可能是另一个问题的问题。

最后一件事:
摆脱将using namespace std;其视为类似于吸烟的习惯。这是一个坏习惯。

于 2012-10-01T02:45:39.087 回答