为什么编译器只从 .cpp 文件生成 object 文件 .o

Why does the compiler only generate object files .o only from .cpp files

如标题所述:为什么编译器仅生成 object 文件 .o 仅来自 .cpp 文件而不是 header 文件?如果实现在 .h 文件中,linker 如何知道如何将 link object 文件放在一起?

Why does the compiler only generate object files .o only from .cpp files not header files

为了具体起见,我假设编译器是 GCC 的 C++ 编译器。

编译器会将header文件编译成object文件,如果你使 清楚那才是你真正想要的。

header.h

#ifndef HEADER_H
#define HEADER_H

#include <iostream>

inline void hw()
{
    std::cout << "Hello World" << std::endl;
}

#endif

如果你只是这样做:

$ g++ header.h

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

这是一个合理的假设,因为通常我们不想编译一个 header 文件直接到 object 文件。通常,我们不想编译一个 header 文件 完全直接,如果我们这样做,我们想要的是一个 pre-compiled header 文件。

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

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

表示:编译,不 linking,header.h,将其视为 C++ 源文件。 输出为 header.o.

然而,这个header.o 非常没用。例如,它不导出 单独的函数 hw 到 linker,因为该函数是内联的。要是我们 查看 object 文件中的符号:

$ 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 <iostream> 引入的东西。

我们可以通过删除关键字inline使header.h适合link年龄。 那么如果我们像以前一样重新编译再看一下:

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

我们已经出口了hw()!我们可以在程序中linkheader.o

main.cpp

extern void hw();

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

编译:

$ g++ -c main.cpp

Link:

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

运行:

$ ./prog
Hello World

但是有一个障碍。现在我们已经在 header.h 中定义了 hw() 这样 link人可以看到,我们不能像header文件那样使用header.h 通常再使用 ,即我们不能 #include "header.h" 超过 一个 .cpp 文件,在同一个程序中一起编译和 linked:

main1.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

一切顺利。所以 link:

% 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.obar.o 中再次出现,这是一个 link 年龄错误: link你不能选择一个定义而不是另一个。

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

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

内编译和 linked 一次

我们从 开始的 header.h 一个合适的 header 文件,根据这个 惯例。我们从中删除 inlineheader.h 不再是 和 header 根据此约定归档。我们应该将其重命名为 .cpp, 如果我们不只是喜欢混淆别人。

How the linker knows how to link object files together if the implementation is in .h files

link er link 只是 object 文件和库。它什么都不知道 关于 .cpp 文件或 .h 文件:就 link 而言,它们可能还不存在 被关注到。 header 文件中的 "implementation" 可以通过三种方式获取 linker.

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

2) 通常的方法,通过 #include-ing 将 header 文件放入 .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 在它甚至开始生成 object 代码之前,你可以看到编译器在之后看到了什么 预处理器通过告诉编译器进行预处理而不是其他任何事情来完成:

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

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

位置的翻译单元

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

3) 通过将 header.h 文件编译成 pre-compiled header.h.gch。这 header.h.gch 是一个"semi-compiled" 形式的 header.h 将被 #include 编辑, 如果存在,每当出现 #include "header.h"#include <header.h> 在代码中。唯一的区别是 semi-compiled header.h.gch 可以 处理速度比 header.h 更快:(3) 只是 (2) 的一个更快的版本(并且它的限制是编译器每次编译只接受一个预编译 header。)

是否通过 (1)(2)(3), link代码时代 .h 文件中的代码与 .cpp 文件中的 link 代码年龄没有什么不同。 所有代码都由编译器编译。编译器不关心代码是否 源自 .h 文件或 .cpp 文件。编译器生成 object 个文件, link er link 是 object 文件。