假设我有一个带有函数声明的头文件:
test.h:
int func(int a);
main.c中:
#include "test.h"
int main {
return func(5);
}
test.c(不包含在test.h中):
int func(int x) {
return x*x;
}
我理解为什么两个文件都编译,但我认为因为 test.c 没有包含到标题中,链接器无法识别出这是实施,但确实如此。
答案 0 :(得分:3)
当链接发生时,头文件早已消失。链接器适用于所谓的目标文件。从每个转换单元编译目标文件,即在我们的例子中是C文件。链接器将解析未在给定目标文件中定义的符号,链接器将查看所有其他目标文件并尝试解析该符号。
在我们的示例中,test.c
已编译为test.o
,并定义了单个符号:func
。 main.c
被编译为main.o
,其定义main
符号,并引用外部符号func
。然后test.o
和main.o
被输入链接器(从main
开始)将从func
解析test.o
。
答案 1 :(得分:2)
头文件只是一个预处理器。您正在寻找的术语是translation unit,它是一个完全预处理的源文件,其中包含所有标头。这是转换单元,编译器看到并使用它作为输入(实际上它比这更复杂,但让它保持简单)来创建链接器要使用的目标文件。
链接器知道“头文件”的 nothing 。相反,它检查目标文件,它是一种特殊格式,包含所需的所有信息,如导出符号表和未定义但引用符号的其他表,然后链接器使用来自所有目标文件和所有库的此信息来构造最终的可执行程序。
因此,在从main.c
源文件生成的目标文件中,有一个表“使用了符号func
但未在此处定义”,并且在{{1}的目标文件中源文件有一个表“这里定义了符号test.c
”。当链接器查看目标文件时,它可以将一个目标文件中func
的用法与另一个目标文件中的定义相匹配。
答案 2 :(得分:1)
头文件告诉C源文件有关共享信息的信息。这些可以是:
类型定义,例如结构;
功能原型。原型告诉返回类型是什么以及参数及其类型是什么。这有助于编译器检查您是否正确使用函数的返回和参数。如果没有原型,编译器将假定返回类型为{
"blabla": true,
"globals": {
"md5": true
}
}
,并且函数可以包含任何数量和类型的参数;
由int
和宏创建的符号常量;
全局变量的名称和类型。
在编译单元中包含头文件(C源文件)可帮助您与编译单元共享此信息。
编译器将编译单元,无论是否使用包含文件,并且留下了许多不在当前编译单元中的符号(变量和函数)。在目标文件中,它注意到了这些。现在,当着墨者收集所有目标文件以创建可执行文件时,它将在这些目标文件和库中搜索未解析的符号。如果找不到任何这些符号,则不会创建任何可执行文件。
所以不,编译器不需要头文件。
答案 3 :(得分:1)
如果您想了解链接器正在做什么,请查看其输入。
首先编译(?<=://)
和://
。然后使用[\w-]+
或main.o
检查它们:您会看到test.o
具有未定义的符号nm
,而objdump
具有相同名称的已定义符号
链接器永远不会看到您的代码,标题或除中间对象文件之外的任何内容。它需要的一切都在那里,它唯一匹配的是符号名称和类型。
请注意,在C中,符号中甚至没有关于函数的参数数量,类型或返回值的信息。如果您更改main.o
以声明func
采用两个参数,程序仍将链接并运行 - 至少,它将开始运行但可能会崩溃。如果它存活下来,其中一个论点将是未初始化的。这种不匹配(在test.o
的声明和定义之间)是为什么建议在test.c
中包含标头的原因,因此编译器可以在链接器做一些愚蠢的事情之前捕获你的错误。
答案 4 :(得分:0)
链接器仅查看名称func
。 main.o
需要它。 test.o
提供了它。所以它有效!
但是...
实施文件应始终包含自己的标题!
我总是在.c
文件中包含头文件,以确保声明和定义具有匹配的签名。
我们假设您更改了定义并忘记更改标题。如果您还没有从test.h
中包含test.c
,则编译器无法看到声明和定义不同。但是当你运行你的程序时,你会得到未定义的行为。
答案 5 :(得分:0)
经过这么多答案后,没有太多要补充的内容,即使它看起来有点偏离主题,你也可能想知道装饰。 应用于符号名称的装饰或错位用作将支票从源转移到对象的简单方法。
MS模块中的MS,对于__stdcall函数,只需在常规名称之后添加@
符号,后跟所用的总字节数:
int func(char *) _func@4
int func(double) _func@8
对于C ++,机制更强大,它们在符号名称中添加了一些字符,这些字符表示从函数返回的变量类型,类,命名空间以及它所采用的参数(see)。
结果是完全合格的&#39;功能名称。通过这种方式,错误的符号很难与函数相关联。
答案 6 :(得分:-1)
链接器并不真正关心标题......
你只需要标题告诉编译器main.c可以使用func()因为会在那里
链接器只接受所有符号并将它们放入一个可执行文件中。
注意:这是一个简单的视图