如果我们有两个.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.o
和sub.o
,这让我认为"两个源文件因此是两个翻译单元。">
我误解了哪一部分?或者我在哪里犯错误?
将可执行文件的生成视为两步过程:首先,将每个翻译单元编译为一个对象文件;让我们称之为编译器。第二,将对象文件链接到一个可执行程序;让我们称之为链接器。
"翻译单元"是第一步。翻译单元是编译开始的每个文件(即传递给编译器的文件)。在大多数IDE中,有一些规则声明每个扩展名为.c
或.cpp
的文件都作为输入传递给编译器,而其他文件则不是。因此,扩展名为.h
、.hpp
、.txt
的文件通常不会直接传递给编译器。
在您的示例中,main.c
和sub.c
可能是翻译单元,而sub.h
本身不是翻译单元(它只是"包含"在其他翻译单元中,并在编译过程中考虑)。
所以你得到了两个对象文件,每个翻译单元一个。然后链接器会考虑这两个对象文件。
注意,即使是.h
文件也可能包含一个完整的程序;但是,除非您将环境配置为该.h
文件是自己编译的,否则它不会生成对象文件。
以下是C标准对此的说明:
一个源文件以及通过预处理指令
#include
包含的所有头文件和源文件被称为预处理翻译单元。在预处理之后,预处理翻译单元被称为翻译单元。[..]以前翻译的译文单元可以单独保存,也可以保存在库中。程序的独立翻译单元通过(例如)调用标识符具有外部链接的函数、操作标识符具有外部连接的对象或操作数据文件来进行通信。翻译单元可以被单独翻译,然后随后被链接以产生可执行程序。
(来源:C99标准草案,5.1.1.1§1)
所以在这两种情况下,你都有两个翻译单元。其中一个来自编译器预处理main.c
和#include
指令中包含的所有内容—即CCD_ 19和可能的CCD_。第二个来自编译器对sub.c
做同样的事情。
第一个例子和第二个例子的不同之处在于,在第二个示例中,您将"不同的翻译单位"显式存储为对象文件。
请注意,没有将一个对象文件与任意数量的转换单元关联的规则。GNU链接器是能够将两个.o
文件连接在一起的链接器的一个示例。
据我所知,该标准没有指定源文件的扩展名。尽管如此,在实际应用中,您可以自由地将#include
、.c
文件转换为其他文件,或者将整个程序放在.h
文件中。使用gcc
,可以使用选项-x c
强制将.h
文件视为翻译单元的起点。
这里的区别:
通过预处理指令
#include
[…]包含的源文件以及所有头文件和源文件
是因为标头不必是源文件。类似地,#include
指令中<...>
的内容不需要是有效的文件名。编译器如何准确地使用命名头<...>
和"..."
是实现定义的。