编译器如何从头文件中知道源文件存在于某处?

How a compiler knows from a header file, that a source file exists somewhere?

刚开始学C/C++,有点迷茫。我经常看到 #include 预处理器指令,带有一个头文件参数:

#include <stdio.h>
#include <iostream.h> 
#include "windows.h"
#include <math.h>

但有时 .h 会丢失:

#include <iostream>

我已经知道,头文件只包含函数的签名和一些预处理器命令。但是编译器何时以及如何包含数学函数,如果我包含 math.h?

如果我创建一个库,那么 .c/.cpp/.h 文件放在哪里,如何包含它们?

实际上,编译器对其他源文件一无所知,它只知道当前 translation unit. It's the job of the linker 获取所有翻译单元和 link 它们与库一起创建可执行文件程序,linker 将解析所有定义并通知缺少的函数定义。

传统上,使用 C 或 C++ 是一个四步过程:

  1. 编辑文件
  2. 将源文件编译成目标文件
  3. Link 生成可执行程序的目标文件和库
  4. 运行 程序。

预处理器通常内置于编译器中,因此预处理和编译只是一步。


至于 C++ 的 "missing" .h、none - 只有标准库头文件具有该扩展名。

回复

How a compiler knows from a header file, that a source file exists somewhere?

通常,编译器如何定位 header 个文件,或者它们是否 个文件,取决于编译器。

但通常会使用以下一种或多种机制:

  • 编译器知道它自己的程序位置,并且可以查找 include 目录 以获取标准 header 相对于该位置的目录。 g++ 这样做。

  • 编译器可以配置路径以将目录包含在一个或多个环境变量中。例如。 g++ 使用 CPATH(以及其他),而 Visual C++ 使用 INCLUDE(可能更多)。

  • 编译器可以接受包含目录路径作为命令行选项。通常这是 -I 或使用 Visual C++,或者 /I.

  • 可以通过众所周知位置的配置文件配置编译器。例如,g++ 使用 specs 文件。

  • 编译器可以接受一个或多个配置文件作为命令行选项。例如。 Visual C++ 接受所谓的 "response files",而 g++ 接受规范文件,尽管语义与一般配置规范文件略有不同。

可能还有更多机制在使用,但以上是最常见的。


回复

But when and how does the compiler include the math functions, if I include math.h?

没有。

无论您包含什么 header,都会 link 编辑整个标准库。根据实施的质量,标准库中未使用的部分将被丢弃。

当你使用第 3rd 方库时,你通常必须明确地 link 使用该库,除了包含它的一个或多个 header秒。 Visual C++ 编译器支持 #pragma 指令,这些指令可以放在 header 中并用于自动引导 linker。但这不是标准化的,例如g++ 不支持该自动机制。


回复

If I create a library, then where to put the .c/.cpp/.h files, and how to include them?

基本上你可以自由发挥,你可以随心所欲地做。这是有问题的,因为 C++ 库的用户必须处理每个库的不同约定和工具,以至于使用不同的 OS 到 "configure" 东西。不过现在流行用CMake做库,单独编译,这样可以减少DIY的问题。

一个不错的选择是创建一个仅 header 的库 ,所有代码都在 header 中。 Boost 的大部分仅 header。优点是它非常易于使用,无需配置或工具集调整或其他任何东西,但使用当前流行的 1950 年代构建技术,它可能会导致构建时间更长。

标准模板库headers,例如包含模板函数的源代码,这些模板函数根据调用模板函数的源代码中涉及的模板类型在编译时实例化.

库是单个库文件中的 collection 个 object 个文件。 linker 通常只包含 object 文件,以满足构建程序所需的函数或数据引用,而不是整个库文件。动态 link 库是个例外,因为所有该库都将在程序加载时加载(如果尚未加载)。

编译器使用 < > 和“ ”为 headers 设置了默认位置。 headers 使用 " ",可以包含相对或绝对路径。相对路径可能因工具集而异。

安装编译器时,标准 header 和库文件也会安装到 well-known 位置。例如,在 Linux 或 Unix 系统上,标准 header 通常安装在 /usr/include/usr/local/include 下,标准库通常安装在 /usr/lib 下或 /usr/local/lib

当您 #include header 文件时,编译器会根据您是使用尖括号 (#include <stdio.h>) 还是引号 (#include "myfile.h").例如,如果您在尖括号中包含一个 header 文件,gcc 将在以下目录中搜索该 header:

/usr/local/include
<em>libdir</em>/gcc/<em>target</em>/<em>version</em>/include
/usr/<em>target</em>/include
/usr/include

如果用引号包含 header 文件,gcc 将首先搜索当前工作目录,然后是 command-line 选项指定的其他目录,最后是标准包含路径多于。

大多数编译器允许您指定额外的包含路径作为编译命令的参数,例如

gcc -o <em>executable-name</em> <strong>-I /<em>additional</em>/<em>include</em>/<em>path</em></strong> <em>source-files</em>

But sometimes the .h is missing:

#include <iostream>

这是C++约定1; C++ 标准 header 没有 .h 扩展,我想让它 看起来 比实际的 object-oriented 多(也就是说,你可以假设您正在直接导入 class,而不是包含描述 class 的文件文本)。

I already know, that the header file contains the signatures of the functions only, and some preprocessor commands. But when and how does the compiler include the math functions, if I include math.h?

数学函数的实现通常保存在一个单独的库中(代码已经编译并存档到一个二进制文件中);根据编译器的不同,数学库可能会也可能不会自动 linked 到可执行文件中。例如,gcc 不会在任何数学库函数中自动 link,即使您包含 math.h header 文件也是如此。您必须明确地将数学库作为构建命令的一部分包含在内:

gcc -o <em>executable-name</em> <em>command-line-options</em> <em>source-files</em> <strong>-lm</strong>

对于 gcc,惯例是 -l<em>name</em> links 在名为 lib<em>name</em>.a2.标准数学库文件名为 libm.a,因此 -lmlibm.a 中包含的机器代码中告诉 gcc 到 link。

与标准 header 一样,编译器将在 well-known 位置搜索标准库。您可以为不属于标准安装的库指定其他搜索路径,如下所示:

gcc -o <em>executable-name</em> <em>command-line-options</em> <em>source-files</em> -L /<em>additional</em>/<em>library</em>/<em>path</em> -l<em>additional-library</em>

If I create a library, then where to put the .c/.cpp/.h files, and how to include them?

您几乎可以将文件放在任何您想要的地方;您将在构建命令中指定它们的位置。因此,假设您要创建一个名为 mylib 的新库。您在您的主目录下创建一个新目录:

mkdir ~/mylib
cd ~/mylib

然后您创建并编辑您的源文件和 headers:

cd ~/mylib
vi mylib.h
vi mylib.c

要将 mylib 构建为静态库,请执行以下操作:

cd ~/mylib
gcc -c mylib.c
ar libmylib.a mylib.o

完成后,目录 ~/mylib 包含以下内容:

libmylib.a
mylib.c 
mylib.h
mylib.o

要在程序中使用 mylib 中收集的函数,您需要指定 header 和库包含路径作为编译命令的一部分:

gcc -o <em>executable-name</em> <em>command-line-options</em> <em>source-files</em> -I ~/mylib -L ~/mylib -lmylib

所以,如果你在另一个程序中 include "mylib.h",这将告诉编译器在 ~/mylib 中搜索那个 header,它会告诉 linker libmylib.a 库将在 ~/mylib 下找到。

在 real-world 情况下,您将使用 makefile 处理所有构建,而不是手动构建所有内容。您还可能设置它,以便作为构建过程的一部分,所有其他人需要使用您的库的 header 将被复制到一个单独的 include 子目录,并且库将被写入一个单独的 lib 子目录,因此您可以在不公开源代码的情况下分发您的库。在那种情况下,上面的构建命令看起来更像:

gcc -o <em>executable-name</em> <em>command-line-options</em> <em>source-files</em> -I ~/mylib/include -L ~/mylib/lib -lmylib


1.情况并非总是如此; C++ 的最早实现使用 iostream.hvector.h 等。我不确定该约定何时更改,但已经有一段时间了。

2. 实际上,我已经不确定这是gcc约定还是*nix约定;两个人的臀部连在一起太久了,有时很难记住。