10

我阅读了一些帖子并得出结论,extern 告诉编译器“这个函数存在,但它的代码在其他地方。不要惊慌。” 但是链接器如何知道函数的定义位置。

我的案例:- 我正在研究 Keil uvision 4。有一个头文件 grlib.h,主要功能在 grlib_demo.c 中(它包括 grlib.h)。现在,在 Circle.c 中定义并在 grlib_demo.c 中调用的函数 GrCircleDraw(),还有一条语句

extern void GrCircleDraw(所有参数);

在 grlib.h 中。我的查询是链接器如何知道 GrCircleDraw() 的定义在哪里,因为 Circle.c 不包含在 grlib.h 和 grlib_demo.c

注意:- 文件 grlib.h 和 Circle.c 在同一个文件夹中。代码运行成功。

4

4 回答 4

9

简单的答案是“编译器不需要知道,但链接器必须能够找到它”。通过多个.o文件或库,链接器必须能够找到GrCircleDraw函数的单个定义。

于 2012-10-14T03:37:07.090 回答
8

当您以ELF 格式编译 .o 文件时,文件中有许多内容,.o例如:

  • .text包含代码的部分;
  • .data, .rodata,.rss包含全局变量的部分;
  • a.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 本地默认值
[...]
     8: 0000000000000000 21 FUNC 全局默认值 1 foo
     9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND GrCircleDraw
    10: 0000000000000015 11 FUNC 全局默认值 1 bla

我们的foo函数有一个符号,一个bla. 值字段给出了它们在该.text部分中的位置。

使用的符号有一个符号GrCircleDraw:它是未定义的,因为这个函数没有在这个.o文件中定义,但仍然可以在其他地方找到。

.text( .rela.text) 部分的重定位表中,您可以找到:

偏移 0x260 处的重定位节“.rela.text”包含 1 个条目:
  偏移信息类型 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 全局默认值 1 GrCircleDraw

它有一个条目用于GrCircleDraw定义其在其.text部分中的位置。

将它们连接在一起

因此,当链接编辑器将两个文件组合在一起时,它知道:

  • 在哪个.o文件中定义了哪些函数及其位置;
  • 在调用者的代码中,它必须使用被调用者的地址进行更新。
于 2015-08-18T21:56:15.083 回答
5

编译器只是将extern函数的名称放入.obj文件中。编译器不需要了解更多。

当您开始链接时,作为开发人员,您有责任将所有必要的目标文件和库文件提供给链接器。链接器会将所有这些函数排列成一个二进制文件。如果您没有指定正确的库或.obj文件,则链接将简单地失败并显示unresolved blah-blah.

默认库通常被隐式包含。这使事情复杂化并产生幻觉。您始终可以指定不想要任何隐式库并明确包含所有内容。不幸的是,每个系统都以自己的方式做到这一点。

于 2012-10-14T03:49:29.827 回答
0

链接通常以这种方式发生:迭代命令行并且给定的每个参数都是

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

最后,必须满足每个引用才能成功链接。链接器命令行中给出的行顺序很重要。

于 2012-10-14T04:20:16.240 回答