【问题标题】:Why does the compiler only generate object files .o only from .cpp files为什么编译器只从 .cpp 文件生成目标文件 .o
【发布时间】:2016-03-17 09:22:32
【问题描述】:

正如标题所说:为什么编译器只从 .cpp 文件而不是头文件生成目标文件 .o ?如果实现在 .h 文件中,链接器如何知道如何将目标文件链接在一起?

【问题讨论】:

    标签: compiler-construction linker header-files object-files


    【解决方案1】:

    为什么编译器只从.cpp文件而不是头文件生成目标文件.o

    具体而言,我假设编译器是 GCC 的 C++ 编译器。

    编译器会将头文件编译成目标文件,如果你使 清楚这就是你真正想要的。

    header.h

    #ifndef HEADER_H
    #define HEADER_H
    
    #include <iostream>
    
    inline void hw()
    {
        std::cout << "Hello World" << std::endl;
    }
    
    #endif
    

    如果你只是这样做:

    $ g++ header.h
    

    那么它不会生成目标文件,因为它假定来自.h 您不想要的扩展名。相反,它会生成 precompiled header file, header.h.gch.

    这是一个合理的假设,因为通常我们不想编译一个 头文件直接复制到目标文件。通常,我们不想编译头文件 完全直接,如果我们这样做,我们想要的是一个预编译的头文件。

    但如果你真的想将header.h 编译成header.o,你可以坚持 像这样:

    $ g++ -c -x c++ header.h
    

    其中说:编译,不链接,header.h,将其视为 C++ 源文件。 输出为header.o

    不过,这个header.o 毫无用处。例如,它不会导出 单独的函数hw 到链接器,因为该函数是内联的。要是我们 查看目标文件中的符号:

    $ objdump -C -t header.o
    
    header.o:     file format elf64-x86-64
    
    SYMBOL TABLE:
    0000000000000000 l    df *ABS*  0000000000000000 header.h
    0000000000000000 l    d  .text  0000000000000000 .text
    0000000000000000 l    d  .data  0000000000000000 .data
    0000000000000000 l    d  .bss   0000000000000000 .bss
    0000000000000000 l     O .bss   0000000000000001 std::__ioinit
    0000000000000000 l     F .text  000000000000003e __static_initialization_and_destruction_0(int, int)
    000000000000003e l     F .text  0000000000000015 _GLOBAL__sub_I_header.h
    0000000000000000 l    d  .init_array    0000000000000000 .init_array
    0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
    0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
    0000000000000000 l    d  .comment   0000000000000000 .comment
    0000000000000000         *UND*  0000000000000000 std::ios_base::Init::Init()
    0000000000000000         *UND*  0000000000000000 .hidden __dso_handle
    0000000000000000         *UND*  0000000000000000 std::ios_base::Init::~Init()
    0000000000000000         *UND*  0000000000000000 __cxa_atexit
    

    我们看到除了样板文件和#include &lt;iostream&gt; 引入的东西之外什么都没有。

    我们可以通过删除关键字inline 使header.h 可用于链接。 那么如果我们像以前一样重新编译,再看看:

    $ objdump -C -t header.o | grep hw
    0000000000000061 l     F .text  0000000000000015 _GLOBAL__sub_I__Z2hwv
    0000000000000000 g     F .text  0000000000000023 hw() 
    

    我们已导出hw()!我们可以在程序中链接header.o

    ma​​in.cpp

    extern void hw();
    
    int main()
    {
        hw();
        return 0;
    }
    

    编译:

    $ g++ -c main.cpp
    

    链接:

    $ g++ -o prog main.o header.o
    

    运行:

    $ ./prog
    Hello World
    

    但是有一个障碍。现在我们已经在header.h 中定义了hw(),这样 链接器可以看到,我们不能使用header.h头文件的方式 通常使用更多,即我们不能#include "header.h"超过 一个.cpp 在同一程序中编译并链接在一起的文件:

    ma​​in1.cpp

    extern void foo();
    extern void bar();
    
    int main()
    {
      foo();
      bar();
      return 0;
    }
    

    foo.cpp

    #include "header.h"
    
    void foo(){
        hw();
    };
    

    bar.cpp

    #include "header.h"
    
    void bar(){
        hw();
    };
    

    全部编译:

    $ g++ -c main1.cpp foo.cpp bar.cpp
    

    一切都好。所以链接:

    % g++ -o prog main1.o foo.o bar.o
    bar.o: In function `hw()':
    bar.cpp:(.text+0x0): multiple definition of `hw()'
    foo.o:foo.cpp:(.text+0x0): first defined here
    collect2: error: ld returned 1 exit status
    

    不好,因为hw() 被定义了两次,一次在foo.o 再次在bar.o 中,这是一个链接错误: 链接器不能选择一个定义而不是另一个。

    所以你看到编译器愿意并且能够编译一个.h 如果您坚持,将文件作为 C++ 源文件;它有能力并且愿意 如果您坚持,请将 .blahblah 文件编译为 C++ 源代码, 假设.blahblah 文件中有合法的 C++。但是一个标题 编译为目标文件的文件对我们几乎没有用处。

    .h 文件和.cpp 文件的区别只是常规的 我们打算如何使用文件。要是我们 给出一个.h 扩展名,我们说的是:这个文件中的所有 C++ 都可以安全地 包含在编译和链接的多个翻译单元(.cpp 文件)中 一起。如果我们给它一个.cpp 扩展名,我们会说:至少有一些 C++ 在这个 文件只能在同一个链接中编译和链接一次。

    我们从 开始的header.h 是一个合适的头文件,根据这个 习俗。我们从中删除 inlineheader.h 不再是标题 按照这个约定归档。我们应该把它重命名为.cpp, 如果我们不只是喜欢让人困惑的话。

    如果实现在 .h 文件中,链接器如何知道如何将目标文件链接在一起

    链接器只链接目标文件和库。它什么都不知道 关于.cpp 文件或.h 文件:就链接器而言,它们可能不存在 被关注到。头文件中的“实现”可以通过三种方式实现 链接器。

    1) 我们刚才讨论的非常规方式:通过编译头文件 到链接的目标文件。如您所见,没有技术 这样做的问题,尽管在实践中从未这样做过。

    2) 通常的方式,通过#include-ing 将头文件放入.cpp 文件中。

    hello.h

    #ifndef HELLO_H
    #define HELLO_H
    
    static char const * hw = "Hello world";
    
    #endif
    

    hello.cpp

    #include "hello.h"
    char const * hello = hw;
    

    在这种情况下,编译器preprocesses hello.cpp 在它甚至开始生成目标代码之前,你就可以看到编译器看到的内容 预处理器通过告诉编译器进行预处理而完成,仅此而已:

    $ g++ -P -E hello.cpp
    static char const * hw = "Hello world";
    char const * hello = hw;
    

    该命令的输出是将编译成的翻译单元 hello.o,如您所见,hello.h 中的代码被简单地复制到 翻译单元代替#include "hello.h"

    所以当编译器开始生成hello.o时,头文件hello.h 无关紧要:它还不如不存在。

    3) 通过将header.h 文件编译成预编译的header.h.gch。这 header.h.gchheader.h 的“半编译”形式,即#include-ed, 如果存在,每当出现#include "header.h"#include &lt;header.h&gt; 在代码中。唯一不同的是半编译后的header.h.gch可以 比header.h 处理得更快:(3) 只是 (2) 的一个更快版本(并且它的限制是编译器每次只接受一个预编译头文件编译。)

    无论是通过(1)(2)还是(3),代码的链接 来自.h 文件的代码链接与来自.cpp 文件的代码链接没有区别。 所有代码都由编译器编译。编译器不在乎代码是否 源自.h 文件或.cpp 文件。编译器生成目标文件, 链接器链接目标文件。

    【讨论】:

    • 现在我明白了,实际上编译一个项目,编译器只关心 .cpp 文件,因为其他工作的一半是由预处理器完成的。以及如何在编译器中使用这些命令?我只是一个初学者,我使用visual studio和tank的
    • @MekacherAnis 您不能将 GCC 命令与 Visual Studio 编译器一起使用:它们是具有完全不同命令行选项的不同编译器。但是一个命令行编译器和链接器“隐藏在”VC++ 中,您可以在Visual Studio 命令提示符下使用它们。开始herehere。您还可以从各种来源下载和安装适用于 Windows 的 GCC C++,例如TDM-GCC是开源软件。
    • @MekacherAnis 或者您可以在 Windows 上安装一个基本的类 linux 环境:CygwinMSYS2 并在该环境中使用 GCC,这是一个更自然的使用环境。跨度>
    猜你喜欢
    • 2016-05-28
    • 2011-07-14
    • 1970-01-01
    • 1970-01-01
    • 2012-02-06
    • 1970-01-01
    • 2014-08-02
    • 1970-01-01
    • 2012-12-23
    相关资源
    最近更新 更多