【问题标题】:How does the linker know where is the definition of an extern function?链接器如何知道外部函数的定义在哪里?
【发布时间】:2012-10-14 03:35:38
【问题描述】:

我阅读了一些帖子并得出结论,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 在同一个文件夹中。代码运行成功。

【问题讨论】:

  • 欢迎使用神奇的链接器
  • 在某一层,编译器不知道;查找函数是链接器的工作。链接器知道,因为你告诉它在哪里寻找它——要么在命令行的目标文件中,要么在库中。
  • @jozefg 你是说黑魔法吧?

标签: c++ c extern keil


【解决方案1】:

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

【讨论】:

  • 废话,我写错了Compiler,当然必须是linker。但是链接器必须知道以某种形式定义的位置。在我的代码 Circle.c 中(其中包括未在任何地方添加的定义)。链接器怎么可能仍然找到它?
  • 链接器必须找到至少一个GrCircleDraw 的定义。通常,它会忽略第一个定义之后的任何额外定义,但如果定义在两个目标文件中,或者如果您正在与静态库链接并且库中包含第二个定义的目标文件,它将反对满足其他符号;它会抱怨双重定义。
  • @JonathanLeffler 没有第二个定义。我的问题是链接器如何找到那个定义。
  • @AnkitGupta:所以你告诉我们:(1)有一个circle.c,但它没有被编译; (2)GrCircleDrawonly定义在circle.c中; (3) 您的程序编译、链接和运行成功。我不相信所有这些都是真的。特别是,我怀疑 (2),并且确实有另一个定义 GrCircleDraw,可能在您链接的库文件中。
  • 您的链接器应该附带一些实用程序来帮助解决此类问题。我认为用于此的 Visual C++ 工具称为dumpbin.exe,但您还没有说您使用的是哪个编译器工具链。另一种方法是让您的链接器创建一个映射文件输出,其中显示了有关链接器如何解析您的程序的大量详细信息。
【解决方案2】:

当你在ELF format 中编译一个.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 本地默认值
[...]
     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文件中定义了哪些函数及其位置;
  • 在调用者的代码中必须使用被调用者的地址进行更新。

【讨论】:

  • 直到我现在才相信没有投票。这是一个精心设计的答案,我很高兴看到这样的 ELF 故障。
【解决方案3】:

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

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

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

【讨论】:

  • 有问题的代码是示例代码。我只需要知道 Linker 如何知道 GrCircleDraw() 的定义在哪里,因为 Circle.c 不包含在任何文件中。
  • 由于你的函数原型在grlib.h,这个函数的主体很可能在grlib.libgrlib.a。查看您的构建日志或.map 文件。他们可能会给你一个线索。
【解决方案4】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 2021-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-07
    相关资源
    最近更新 更多