链接器如何知道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文件及其位置;
  • 在调用者的代码中,它必须使用被调用者的地址进行更新。

链接通常以这种方式发生:迭代命令行并给出每个参数

  1. 如果是目标文件则直接使用,
  2. 在需要的范围内使用(=实现到目前为止尚未解决的所有参考文献)。

最后,必须完成每个参考才能成功链接。 链接器命令行中给出的行的顺序很重要。