链接器如何知道extern函数的定义在哪里?
我读了几篇post并得出结论,extern告诉编译器“这个函数存在,但它的代码在其他地方。不要惊慌。” 但链接器如何知道函数的定义位置。
我的案例: – 我正在研究Keil uvision 4.有一个头文件grlib.h,主函数在grlib_demo.c(它包括grlib.h)。 现在,有一个函数GrCircleDraw()在Circle.c中定义并在grlib_demo.c中调用,还有一个语句
extern void GrCircleDraw(所有参数);
在grlib.h中。 我的查询是链接器如何知道GrCircleDraw()的定义,因为Circle.c不包含在grlib.h和grlib_demo.c中
注意: – 文件grlib.h和Circle.c位于同一文件夹中。 代码运行成功。
简单的答案是“编译器不需要知道,但链接器必须能够找到它”。 通过多个.o
文件或通过库,链接器必须能够找到GrCircleDraw
函数的单个定义。
编译器只将extern
函数的名称放入.obj
文件中。 编译器不需要了解更多信息。
当您开始链接时,作为开发人员,您有责任将所有必需的目标文件和库文件提供给链接器。 链接器会将所有这些function安排到二进制文件中。 如果您没有指定正确的库或.obj
文件,那么链接将会因unresolved blah-blah
失败。
通常隐式包含默认库。 这使事情变得复杂并产生幻想。 您始终可以指定不需要任何隐式库并明确包含所有内容。 不幸的是,每个系统都以自己的方式完成。
当您以ELF格式编译.o文件时,.o文件中有许多内容,例如:
- 包含代码的
.text
部分; - 包含全局变量的
.data
,.rodata
,.rss
部分; -
.symtab
包含.o
中的符号列表(函数,全局变量和其他)(以及它们在文件中的位置)以及.o
文件使用的符号; - 诸如
.rela.text
部分,它们是重定位列表 – 这些是链接编辑器(和/或动态链接器)必须进行的修改,以便将程序的不同部分链接在一起。
在来电方面
让我们编译一个简单的C文件:
extern void GrCircleDraw(int x); int foo() { GrCircleDraw(42); return 3; } int bla() { return 2; }
有:
gcc -o test.o test.c -c
(我正在使用我的系统的本机编译器,但在交叉编译到ARM时它将工作完全相同)。
您可以使用以下命令查看.o文件的内容:
readelf -a test.o
在符号表中,您会发现:
符号表'.symtab'包含10个条目: Num:值大小类型绑定Vis Ndx名称 0:0000000000000000 0 NOTYPE LOCAL DEFAULT UND [...] 8:0000000000000000 21 FUNC GLOBAL DEFAULT 1 foo 9:0000000000000000 0 NOTYPE GLOBAL DEFAULT UND GrCircleDraw 10:0000000000000015 11 FUNC GLOBAL DEFAULT 1 bla
我们的foo
函数有一个符号, bla
有一个符号。 值字段在.text
部分中给出它们的位置。
使用的符号GrCircleDraw
有一个符号:它是未定义的,因为此函数未在此.o
文件中定义,但仍可在其他位置找到。
在.text
部分( .rela.text
)的重定位表中,您会发现:
位于偏移量0x260的重定位部分'.rela.text'包含1个条目: 偏移信息类型Sym。 价值Sym。 姓名+加数 00000000000a 000900000002 R_X86_64_PC32 0000000000000000 GrCircleDraw - 4
该地址在foo
:链接编辑器将使用GrCircleDraw
函数的地址修补此地址处的指令。
在被叫方
现在让我们自己编译一个GrCircleDraw
的实现:
void GrCircleDraw(int x) { }
我们来看看它的符号表:
符号表'.symtab'包含9个条目: Num:值大小类型绑定Vis Ndx名称 [...] 8:0000000000000000 9 FUNC GLOBAL DEFAULT 1 GrCircleDraw
它有一个GrCircleDraw
的条目,在其.text
部分中定义它的位置。
将它们连接在一起
因此,当链接编辑器将两个文件组合在一起时,它知道:
- 哪个函数定义在哪个
.o
文件及其位置; - 在调用者的代码中,它必须使用被调用者的地址进行更新。
链接通常以这种方式发生:迭代命令行并给出每个参数
- 如果是目标文件则直接使用,
- 在需要的范围内使用(=实现到目前为止尚未解决的所有参考文献)。
最后,必须完成每个参考才能成功链接。 链接器命令行中给出的行的顺序很重要。