C:翻译单位说明

C: clarification on translation unit

如果我们有两个 .c 文件和一个 .h 文件:main.c sub.c sub.h,其中

main.c

#include "sub.h"
...

sub.c

#include "sub.h"
...

我们可以用 i)

编译程序
gcc -o a.out main.c sub.c

或 ii)

gcc -c main.c
gcc -c sub.c
gcc -o a.out main.o sub.o

在这种情况下,预处理器输出一个或两个翻译单元吗?

我很困惑,因为:main.c 包含 sub.h,这意味着预处理器将输出一个编译单元。另一方面,在创建可执行文件之前创建了两个目标文件,main.osub.o,让我认为 "two source files thus two translation units."

我误解了哪一部分?或者我哪里出错了?

将可执行文件的生成视为一个两步过程:首先,将每个翻译单元编译为一个目标文件;让我们称之为编译器。其次,目标文件链接在一起成为一个可执行程序;我们称其为链接器。

"Translation unit"是第一步的事情。翻译单元是编译开始的每个文件(即传递给编译器的文件)。在大多数 IDE 中,有一些规则声明每个扩展名为 .c.cpp 的文件都作为输入传递给编译器,而其他文件则不然。因此扩展名为 .h.hpp.txt 的文件通常不会直接传递给编译器。

在您的示例中,main.csub.c 可能是翻译单元,而 sub.h 本身不是翻译单元(在其他翻译单元中只有 "included"并在编制过程中加以考虑)。

所以你得到两个目标文件,每个翻译单元一个。然后链接器会考虑这两个目标文件。

请注意,即使 .h 文件也可能包含完整的程序;但是除非您将环境配置为该 .h 文件是自行编译的,否则它不会生成目标文件。

C 标准对此有如下说明:

A source file together with all the headers and source files included via the preprocessing directive #include is known as a preprocessing translation unit. After preprocessing, a preprocessing translation unit is called a translation unit. [..] Previously translated translation units may be preserved individually or in libraries. The separate translation units of a program communicate by (for example) calls to functions whose identifiers have external linkage, manipulation of objects whose identifiers have external linkage, or manipulation of data files. Translation units may be separately translated and then later linked to produce an executable program.

(来源:C99 标准草案,5.1.1.1 §1)

因此,在您的这两种情况下,您都有两个翻译单元。其中之一来自编译器预处理 main.c 以及通过 #include 指令包含的所有内容,即 sub.h 并且可能 <stdio.h> 和其他 header。第二个来自编译器对 sub.c.

做同样的事情

第一个示例与第二个示例的不同之处在于,在后者中,您将 "different translated translation units" 显式存储为 object 文件。

请注意,没有任何规则可以将一个 object 文件与任意数量的翻译单元相关联。 GNU 链接器是 is capable of joining two .o files together.

链接器的一个示例

据我所知,该标准没有规定源文件的扩展名。尽管如此,在实践方面,您可以自由地将 #include 一个 .c 文件转换成其他文件,或者将您的整个程序放在一个 .h 文件中。使用 gcc,您可以使用选项 -x c 强制将 .h 文件视为翻译单元的起点。

这里所做的区分:

A source file together with all the headers and source files included via the preprocessing directive #include [...]

是因为 header 不一定是源文件。同样,#include 指令中 <...> 的内容不必是有效的文件名。编译器如何使用命名的 headers <...>"..." 是 implementation-defined.