Compiler construction 为什么编译器仅从.cpp文件生成对象文件.o
正如标题所说:为什么编译器只从.cpp文件而不是头文件生成对象文件.o?如果实现在.h文件中,链接器如何知道如何将对象文件链接在一起 为什么编译器只从.cpp文件而不是头文件生成对象文件.o <>具体的,我假设编译器是GCC的C++编译器。 编译器将把头文件编译成目标文件,如果您 很明显,这才是你真正想要的 标题.hCompiler construction 为什么编译器仅从.cpp文件生成对象文件.o,compiler-construction,linker,header-files,object-files,Compiler Construction,Linker,Header Files,Object Files,正如标题所说:为什么编译器只从.cpp文件而不是头文件生成对象文件.o?如果实现在.h文件中,链接器如何知道如何将对象文件链接在一起 为什么编译器只从.cpp文件而不是头文件生成对象文件.o 具体的,我假设编译器是GCC的C++编译器。 编译器将把头文件编译成目标文件,如果您 很明显,这才是你真正想要的 标题.h #ifndef HEADER_H #define HEADER_H #include <iostream> inline void hw() { std::co
#ifndef HEADER_H
#define HEADER_H
#include <iostream>
inline void hw()
{
std::cout << "Hello World" << std::endl;
}
#endif
#ifndef HELLO_H
#define HELLO_H
static char const * hw = "Hello world";
#endif
然后它将不会生成对象文件,因为它假定来自.h
你不希望的扩展。相反,它将产生
A.
header.h.gch
这是一个合理的假设,因为我们通常不想编译
头文件直接指向对象文件。通常,我们不想编译头文件
直接,如果我们这样做,我们需要的是一个预编译的头文件
但如果您确实希望将header.h
编译为header.o
,您可以坚持
在上面这样写:
$ g++ -c -x c++ header.h
:编译,不链接,代码>页眉。h < /C> >,将其视为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
拉进来的东西
我们可以通过删除关键字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;
}
extern void foo();
extern void bar();
int main()
{
foo();
bar();
return 0;
}
#include "header.h"
void foo(){
hw();
};
#include "header.h"
void bar(){
hw();
};
#include "hello.h"
char const * hello = hw;
汇编:
$ g++ -c main.cpp
链接:
运行:
但是有一个障碍。既然我们在header.h
中定义了hw()
,那么
链接器可以看到它,我们不能以头文件的方式使用header.h
通常不再使用,即我们不能将“header.h”包含在超过
在同一程序中编译并链接在一起的一个.cpp
文件:
main1.cpp
extern void hw();
int main()
{
hw();
return 0;
}
extern void foo();
extern void bar();
int main()
{
foo();
bar();
return 0;
}
#include "header.h"
void foo(){
hw();
};
#include "header.h"
void bar(){
hw();
};
#include "hello.h"
char const * hello = hw;
foo.cpp
extern void hw();
int main()
{
hw();
return 0;
}
extern void foo();
extern void bar();
int main()
{
foo();
bar();
return 0;
}
#include "header.h"
void foo(){
hw();
};
#include "header.h"
void bar(){
hw();
};
#include "hello.h"
char const * hello = hw;
bar.cpp
extern void hw();
int main()
{
hw();
return 0;
}
extern void foo();
extern void bar();
int main()
{
foo();
bar();
return 0;
}
#include "header.h"
void foo(){
hw();
};
#include "header.h"
void bar(){
hw();
};
#include "hello.h"
char const * hello = hw;
把它们全部汇编起来:
$ g++ -c main1.cpp foo.cpp bar.cpp
一切都好。So链接:
% 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++源文件;它能够并且愿意
如果你坚持,将一个<代码> .BLALBAH< /COD>文件作为C++源编译,
假设在代码< > BLAHLABA< /COD>文件中有合法C++。但是头球
编译成对象文件的文件对我们几乎没有用处
.h
文件和.cpp
文件之间的区别只是一个常规的区别
关于我们打算如何使用该文件的区别。如果我们
给出一个<代码> .h /代码>扩展,我们可以说:这个文件中的所有C++都可以安全地使用。
包含在多个编译和链接的翻译单元(.cpp
文件)中
在一起如果我们给它一个<代码> .CPP扩展,我们可以说:至少C++中的一些
文件只能在同一链接中编译和链接一次
根据本文,我们开始使用的header.h
是一个合适的头文件
惯例。我们从中删除了inline
的header.h
不再是头
按照这个惯例归档。我们应该把它改名为.cpp
,
如果我们不喜欢迷惑人
如果实现在.h文件中,链接器如何知道如何将对象文件链接在一起
链接器只链接对象文件和库。它什么都不知道
关于.cpp
文件或.h
文件:它们可能与链接器不存在一样
他担心。头文件中的“实现”可以通过三种方式实现
链接器
1)我们刚才讨论的非常规方法:编译头文件
指向已链接的对象文件。正如您所看到的,没有任何技术问题
尽管在实践中从来没有这样做过,但这样做也有问题
2)通常的方法是通过#将头文件包含在.cpp
文件中
你好。h
#ifndef HEADER_H
#define HEADER_H
#include <iostream>
inline void hw()
{
std::cout << "Hello World" << std::endl;
}
#endif
#ifndef HELLO_H
#define HELLO_H
static char const * hw = "Hello world";
#endif
你好。cpp
extern void hw();
int main()
{
hw();
return 0;
}
extern void foo();
extern void bar();
int main()
{
foo();
bar();
return 0;
}
#include "header.h"
void foo(){
hw();
};
#include "header.h"
void bar(){
hw();
};
#include "hello.h"
char const * hello = hw;
在本例中,编译器hello.cpp
甚至在它开始生成目标代码之前,您就可以看到编译器之后看到了什么
预处理器通过告诉编译器进行预处理而不是其他方式完成:
$ g++ -P -E hello.cpp
static char const * hw = "Hello world";
char const * hello = hw;
该命令的输出是将编译成的翻译单元
hello.o
,如您所见,hello.h
中的代码只需复制到
替换的翻译单元包括“hello.h”
因此,当编译器开始生成hello.o
时,头hello.h
与此无关:它还不如不存在
3)将header.h
文件编译成预编译的header.h.gch
。这个
header.h.gch
是header.h
的一种“半编译”形式,将#包括-ed,
如果存在,只要出现#include“header.h”
或#include
在代码中。唯一的区别是半编译的header.h.gch
可以
处理速度比头更快。h
:(3)只是(2)的一个更快版本(并且它有一个限制,编译器每次编译只接受一个预编译头。)
是否是g