2

比如你有main.cpp和mustcomefirst.cpp,main.cpp有入口函数int main()

我认为编译器(通常)像这样工作是否正确:首先它搜索带有入口点函数的文件,然后用要包含的文件中的代码替换所有包含函数行,就好像#include mustcomefirst.cpp不是' t写在文件中,但文件mustcomefirst.cpp中的代码写在那里,然后它进入入口点函数并从那里按顺序连续执行?

如果具有入口点函数的文件不包含该文件(直接或间接),那么在项目中包含文件是否意味着什么?

4

3 回答 3

2

不,编译器通常不关心int main()您的任何源文件中是否有 或它在哪个源文件中(它确实关心您是否有一个无效的main,例如void main())。它肯定不会所有源文件中搜索main.

当您使用三个源文件编译项目时:

g++ file1.cpp file2.cpp file3.cpp

它们中的每一个都被单独翻译成目标代码,就像你做的一样

g++ -c file1.cpp; g++ -c file1.cpp; g++ -c file1.cpp

然后通过链接器链接在一起 - 您可以手动执行此操作

g++ file1.o file2.o file3.o -o myprogram.

在这里,链接器的工作是确保main您的程序中有一个。如果没有,你会得到一个undefined reference错误,但同样,这一切都是在编译完成后很久才发生的。

(当您谈论“在项目中包含文件”时,您的问题变得模棱两可。如果您在谈论#include,则仅包含一个翻译单元中的代码。除此之外,“项目”在 C++ 中不是一个明确定义的概念但是您的 IDE 或构建系统使用的抽象 - 它主要意味着您放置在“项目”中的所有源文件都将被编译然后链接在一起,如我上面的示例所示。)

于 2013-03-23T00:54:42.833 回答
1

首先,从高层次上了解 C++ 程序是如何编译的。如果您的项目中有三个文件 - main.cppfoo.cppfoo.h- 您可以像这样编译项目:

g++ main.cpp foo.cpp

这两个文件是分开编译的。预处理器和编译器根本不关心这些文件之间的关系。预处理和编译main.cpp产生一个目标文件,预处理和编译foo.cpp产生另一个目标文件。

只有在最后一步,即链接步骤中,才会考虑关系。链接器将看到它的main定义main.cpp并将其作为程序的起点。链接器将看到里面有一个对定义main.cpp的函数的调用,并将它们链接在一起。foofoo.cpp

因此,对于您传递给编译器的每个文件,有两个主要步骤:

  1. 预处理 - 以非常原始的方式处理文件,从而产生预处理令牌。其中一些标记是以 开头的预处理指令#,例如#include#define。这些指令被执行。执行#include指令几乎只是将命名文件的内容复制到当前文件中。

    预处理阶段产生所谓的翻译单元

  2. 编译——然后对文件进行句法和语义分析并翻译成目标文件。这就是它开始关心您的代码究竟要做什么以及它是否正确的地方。如果您尝试使用尚未声明的名称,编译器会告诉您。但是,您通常可以使用尚未定义的名称,因为它们可能在另一个翻译单元中定义。

    此编译阶段生成目标文件。

在此之后,通过解析它们之间的引用来组合生成的目标文件。如果对应的目标文件main.cpp使用了foo定义的函数,foo.cpp那么它们将链接在一起,依此类推。


为了更清楚,让我们看一个例子。假设我们有以下源文件:

  • 主文件

    #include "foo.h"
    
    int main() {
      foo();
    }
    
  • foo.h

    void foo();
    
  • foo.cpp

    #include <iostream>
    #include "foo.h"
    
    void foo() {
      std::cout << "Foo!" << std::endl;
    }
    

所以我们在命令行上将main.cppand传递给编译器。foo.cpp请注意,我们不会将头文件传递给编译器。它们仅包含在.cpp文件中。另请注意,.cpp文件通常不包含其他.cpp文件。每个.cpp文件都单独编译并稍后链接。

所以在预处理阶段之后,我们有两个翻译单元:

  • main.cpp翻译单元

    void foo();
    
    int main() {
      foo();
    }
    
  • foo.cpp翻译单元

    // contents of <iostream> here
    void foo();
    
    void foo() {
      std::cout << "Foo!" << std::endl;
    }
    

请注意, 的内容foo.h已被复制到每个文件中。我们现在有两个完全有效的翻译单元。第一个只是定义main函数并调用仅声明的foo函数。第二个首先声明foo然后在之后定义它。

然后编译翻译单元以生成目标文件,通常称为main.ofoo.o. 然后,链接器将查看文件中某些未解析的引用。例如,它会找到对fooin的调用main.o并查看它尚未定义。所以它会查看其他目标文件,看看是否能找到它,果然,它在foo.o.

于 2013-03-23T01:01:51.670 回答
0

编译器不关心入口点函数。加载程序确实(http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html)。

非常广泛地,(对于每个源文件,例如 .cpp/.c)编译器的任务是:

  1. 预处理文件(包括标题(例如,.h、.hpp),并替换宏)。在这个阶段,源文件中包含的所有头文件都包含(复制粘贴的一种)到源文件中。所有源文件都是独立编译的,与其他源文件包含/定义的内容无关。
  2. Parses,将程序转换为抽象语法树
  3. 检查语法和类型错误。
  4. 将程序转换为中间表示(IR)。
  5. 编译器的后端采用 IR 并优化程序。
  6. 之后,后端将优化的 IT 转换为特定于目标的机器代码(目标文件)。

----- 在此之后编译器的工作就完成了。除了其他任务,链接器(http://en.wikipedia.org/wiki/Linker_(computing))只查找程序中是否有main函数。如果您在任何目标文件中都没有 main() 函数,则会出现链接器错误。通常,要链接的一组目标文件中的每个函数都应该有一个且只有一个定义,以生成可执行文件。

----- 可执行文件由可以运行/执行的链接器生成。

----- 启动执行加载器将程序加载到主内存并调用main()函数。

要了解更多信息,只需对可执行文件执行 objdump,您会发现很多函数在 main 之前被调用。

在linux上你可以做

objdump -d your_main_program > dump_file

于 2013-03-23T01:04:43.997 回答