Compiler construction 为什么编译器仅从.cpp文件生成对象文件.o

Compiler 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

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

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

<>具体的,我假设编译器是GCC的C++编译器。 编译器将把头文件编译成目标文件,如果您 很明显,这才是你真正想要的

标题.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
然后它将不会生成对象文件,因为它假定来自
.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