链接器如何知道extern函数的定义在哪里?

时间:2012-10-14 03:35:39

标签: c++ c extern keil

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

4 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:4)

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

当您开始链接时,作为开发人员,您有责任将所有必需的目标文件和库文件提供给链接器。链接器会将所有这些功能安排到二进制文件中。如果您没有指定正确的库或.obj文件,那么链接将会因unresolved blah-blah而失败。

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

答案 2 :(得分:4)

ELF format中编译.o文件时,.o文件中有很多内容,例如:

  • 包含代码的.text部分;
  • .data.rodata.rss包含全局变量的部分;
  • 一个.symtab,其中包含.o中的符号列表(函数,全局变量等)(以及它们在文件中的位置)以及{{1}使用的符号文件;
  • 诸如.o之类的部分,它们是重定位列表 - 这些是链接编辑器(和/或动态链接器)必须进行的修改,以便将程序的不同部分链接在一起。

在来电方

让我们编译一个简单的C文件:

.rela.text

使用:

extern void GrCircleDraw(int x);

int foo()
{
  GrCircleDraw(42);
  return 3;
}

int bla()
{
  return 2;
}

(我正在使用我系统的本机编译器,但在交叉编译到ARM时它的工作方式完全相同)。

您可以使用以下命令查看.o文件的内容:

gcc -o test.o test.c -c

在符号表中,您会找到:

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     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

我们的readelf -a test.o 函数有一个符号,foo有一个符号。值字段在bla部分中显示其位置。

使用的符号.text有一个符号:它未定义,因为此函数未在此GrCircleDraw文件中定义,但仍可在其他位置找到。

.o部分(.text)的重定位表中,您会找到:

Relocation section '.rela.text' at offset 0x260 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000000a  000900000002 R_X86_64_PC32     0000000000000000 GrCircleDraw - 4

此地址在.rela.text范围内:链接编辑器将使用foo函数的地址修补此地址的指令。

在被叫方

现在让我们自己编译GrCircleDraw的实现:

GrCircleDraw

让我们看看它的符号表:

Symbol table '.symtab' contains 9 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
[...]
     8: 0000000000000000     9 FUNC    GLOBAL DEFAULT    1 GrCircleDraw

它有一个void GrCircleDraw(int x) { } 条目,用于在GrCircleDraw部分中定义其位置。

将它们链接在一起

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

  • 哪个函数定义在.text文件及其位置;
  • 在调用者的代码中,必须使用被调用者的地址进行更新。

答案 3 :(得分:0)

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

  1. 直接使用,如果它是目标文件,
  2. 在需要的范围内使用(=完成迄今尚未解决的所有参考文献)。
  3. 最后,必须完成每个参考才能成功链接。链接器命令行中给出的行的顺序很重要。