【发布时间】:2016-03-17 09:22:32
【问题描述】:
正如标题所说:为什么编译器只从 .cpp 文件而不是头文件生成目标文件 .o ?如果实现在 .h 文件中,链接器如何知道如何将目标文件链接在一起?
【问题讨论】:
标签: compiler-construction linker header-files object-files
正如标题所说:为什么编译器只从 .cpp 文件而不是头文件生成目标文件 .o ?如果实现在 .h 文件中,链接器如何知道如何将目标文件链接在一起?
【问题讨论】:
标签: compiler-construction linker header-files object-files
为什么编译器只从.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 <iostream> 引入的东西之外什么都没有。
我们可以通过删除关键字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!
main.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 在同一程序中编译并链接在一起的文件:
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
一切都好。所以链接:
% 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 是一个合适的头文件,根据这个
习俗。我们从中删除 inline 的 header.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.gch 是header.h 的“半编译”形式,即#include-ed,
如果存在,每当出现#include "header.h" 或#include <header.h>
在代码中。唯一不同的是半编译后的header.h.gch可以
比header.h 处理得更快:(3) 只是 (2) 的一个更快版本(并且它的限制是编译器每次只接受一个预编译头文件编译。)
无论是通过(1)、(2)还是(3),代码的链接
来自.h 文件的代码链接与来自.cpp 文件的代码链接没有区别。
所有代码都由编译器编译。编译器不在乎代码是否
源自.h 文件或.cpp 文件。编译器生成目标文件,
链接器链接目标文件。
【讨论】: