在阅读库源代码时,我发现有.c
和.h
个同名文件。在主源中,包含.h
文件,但不包括.c
文件。当我查看.h
文件中的代码时,它也不会#include
同名的.c
文件。我错过了什么吗?当同名的.c
文件为.h
时,编译器是否自动包含#include
文件?
答案 0 :(得分:8)
典型的.h
文件用作.c
文件的“目录”,包括函数原型,typedef等。程序包括.h
,以便他们了解功能和类型。
.c
文件是单独编译的,并生成包含实际代码的目标文件(.o
或.obj
)。
然后,通过Makefile,Project文件或类似方法将目标文件绑定到主程序。然后,目标文件与主程序链接,产生一个功能整体。
对于库, 没有主程序。目标文件收集在一起并以特殊格式(对于静态或动态库)。使用该库的任何程序都将包含上述.h
,并链接到库。
答案 1 :(得分:8)
没有魔力。编译C程序时,有两个主要步骤。
首先,编译的每个编译单元都是隔离的。 (编译单元基本上是一个.c文件,加上它包含的所有内容)。
在这个阶段,它对其他.c文件中包含的内容一无所知,这意味着它无法生成完整的程序。它能做的是生成一些带有“填空”点的代码。如果从foo.c调用一个在bar.h中声明并在bar.c中定义的函数,则编译器只能看到函数存在。它在bar.h中声明,因此我们必须假设完整定义存在 somewher 。但由于该定义位于另一个编译单元内,我们还不能看到它。因此,编译器会生成调用该函数的代码,并在其上注明“在实际知道后填写此函数的地址”。
一旦每个编译单元都以这种方式编译,你就会留下一堆目标文件(如果你用GCC编译,通常是.o,如果你使用MSVC则是.obj),包含这种“填空” “代码。
现在链接器获取所有这些目标文件,并尝试将它们合并在一起,这样就可以填充空白。现在可以找到我们为上面生成调用的函数,因此我们可以将其地址插入到调用中。
因此,如果.c文件与.h具有相同的名称,则不会发生任何特殊情况。这只是一个让人类更容易弄清楚每个文件中的内容的惯例。
编译器不关心。它只需要每个.c文件,以及它包含的任何内容,并将其编译为目标文件。然后链接器将所有这些目标文件合并为一个可执行文件。
答案 2 :(得分:6)
没有。不需要在.h文件中包含.c文件。 .h文件通常不直接作为编译器输入传递,但其唯一目的是包含在其他文件中。
通常,它类似于:
"a.c" includes "a.h"
"b.c" includes "c.h"
编译“a.c”和“b.c”并链接输出文件。
答案 3 :(得分:4)
.h文件包含.c文件中函数的声明。想要使用另一个.c文件的功能的其他.c文件需要这些声明。因此.c文件永远不会包含.c文件。
将每个.c文件编译成目标文件,然后将所有这些目标文件链接到库或应用程序中。
答案 4 :(得分:1)
预处理器没有做任何神奇的事情。
要在包含的目录中包含某些内容(难以使用您自己的标题)
#include <foo.h>
或在同一编译目录中包含某些内容
#include "foo.h"
所有这一切都需要foo.h的文本并将其直接插入输出。 gcc a.c -E将打印出预处理后的样子。
这是一个'愚蠢'的过程,因为编写自己的程序将正确处理#include
是一种微不足道的练习。
答案 5 :(得分:1)
你的问题有点用词不当。您针对* .h文件进行编译,并且忽略内联,您将链接到* .c文件。
如果你回到基础,你可以说:
所以,举个例子:
addInts()函数的实现者可以自由地修改实现addInts()函数的软件。
根本没有修改addInts()函数的接口(即根本不修改addInts.h),然后可以免费分发包含addInts函数的修改实现的新共享库。人们不得不重新编译新的addInts库版本。
哇!为什么阅读这个解释听起来像Danny Kaye和“The Vessel with the Pessel”?
您可能希望了解Bertrand Meyer与Eiffel合作的作品以及“Design by Contract”。
答案 6 :(得分:0)
如果我想用C或C ++创建一个静态库,我会把我的函数原型放在.h中,将实际的函数代码放在.c或.cpp中。之后我编译了我的.cpp来制作一个.lib
要将我的.h链接到我的.lib,我在.h
中编写了这个预处理器命令#pragma comment (lib, "theNameOfTheLibFile.lib")
之后如果我在程序中包含.h,我的程序就知道在哪里可以找到匹配的库。