【问题标题】:the #include directive during the linking stage链接阶段的#include 指令
【发布时间】:2020-03-06 08:17:53
【问题描述】:

头文件通常使用#ifndef 指令(或类似指令)进行一些安全防护,例如:

//header.hpp
#ifndef HEADER
#define HEADER
//code
#endif  

但是,我有一个困惑,如果我们执行以下操作(考虑两个文件的源代码):

//file1.cpp
#include "header.hpp"
//somecode  

和文件

//file2.cpp
#include "header.hpp"
//somecode  

如果我们这样做:

g++ file1.cpp file2.cpp -o mainfile  

我们将获得一个可执行文件,该可执行文件将获得一个没有重复的可执行文件,因为在编译时检查了包含。

但是,如果我们这样做:

g++ -c file1.cpp -o file1.o
g++ -c file2.cpp -o file2.o
g++ 文件1.o 文件2.o -o 主文件.o

在链接阶段会发生什么?包含有冲突吗?在编译期间包含会发生什么?它会重复吗?在这个阶段处理这个问题的底层机制是什么?

【问题讨论】:

  • #include 与链接无关。或者编译。它由预处理器完成。
  • 在链接阶段没有什么特别的事情发生。如果手动将标头的内容复制到源文件中,结果是一样的。
  • @mathmaniage 再次,指令仅在预处理器阶段处理,这发生在编译器阶段之前,发生在链接器阶段之前。链接器没有指令的概念,它只看到编译器产生的代码,而编译器又只看到预处理器在执行宏替换后产生的代码。给定您的示例,编译器将生成包含相同符号的多个目标文件,然后链接器将丢弃重复项,或者如果它们彼此不兼容,则会出错。
  • @mathmaniage 通常是后者,但现在编译器和链接器做了很多聪明的事情。你真的不需要关心,至少在你的水平上。
  • @mathmaniage "你的水平" = "初学者水平"。

标签: c++ linker


【解决方案1】:

通常头文件只包含函数和变量声明,而不包含它们的定义。它是由链接阶段处理的定义,因此可以在不同的源文件中多次包含标头,而链接器将看不到任何内容。如果头文件中有全局函数或变量定义,则会出现链接器错误。

【讨论】:

    【解决方案2】:

    这里的正式术语是“翻译单元”。这就是您所说的单个 .cpp 文件,其中包含所有标头。预处理器定义不跨越翻译单元,这里有两个翻译单元。链接过程是组合翻译单元的过程,但在那个阶段,预处理器已经完成了很长时间。

    【讨论】:

      【解决方案3】:

      两者完全相同。 Guard 不会转到下一个文件。每个文件都使用新的#define - state 编译。

      【讨论】:

      • 你的意思是当两个目标文件被链接时,它们实际上是一起(重新)编译成一个可执行文件?
      【解决方案4】:

      #include 实际上是一个预处理器指令,这意味着它们在 编译之前被解析。预处理器分别处理每个翻译单元,两个翻译单元互不影响。此步骤的结果不包含任何预处理器语句(如#include#ifdef#define 等)。

      所以经过预处理后,file1.cppfile2.cpp 这两个文件都包含header.hpp 的内容。然后两者都编译为file1.ofile2.o。到目前为止没有问题。这就是包含警卫的重要性。如果翻译单元包含重复声明,编译将失败。

      假设你有一个header1.hpp

      #include "header.hpp"
      
      class ABC { ... };
      

      还有header2.hpp:

      #include "header.hpp"
      
      class XYZ { ... };
      

      有些文件,比如file3.cpp 会同时依赖这两者:

      #include "header1.hpp"
      #include "header2.hpp"
      
      class Foo : pulbic ABC, public XYZ {};
      

      如果没有包含保护,你会以包含 header.hpp 两次结束,并在翻译单元中两次获取所有声明,这不会编译。 (我们在此处查看file3.cpp)。使用包含守卫header.hpp 仅包含一次

      现在我们终于到了链接阶段,回到你原来的例子。您有 2 个编译单元,它们都包含来自header.hpp 的所有声明。链接器不会关心重复声明。链接器只有在找到一个符号的多个定义时才会失败。

      #includes 在链接阶段没有被“检查”或“忽略”,它们根本不再存在。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-20
        相关资源
        最近更新 更多