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

nzkunb0c  于 2022-12-15  发布在  其他
关注(0)|答案(4)|浏览(149)

我读了一些帖子,得出的结论是extern告诉编译器“这个函数存在,但是它的代码在别的地方。不要惊慌。”但是链接器怎么知道这个函数在哪里定义呢?
我的案例:-我正在Keil uvision 4上工作。有一个头文件grlib. h,主函数在grlib_demo.c中(它包括grlib. h)。现在,有一个函数GrCircleDraw(),它在Circle. c中定义,并在grlib_demo. c中调用,还有一个语句
外部空GrCircleDraw(所有参数);
我的问题是链接器如何知道GrCircleDraw()的定义在哪里,因为Circle.c不包含在grlib. h和grlib_demo. c中
注意:-文件grlib. h和Circle. c在同一个文件夹中。代码运行成功。

sczxawaw

sczxawaw1#

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

n6lpvg4x

n6lpvg4x2#

当您在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

在符号表中,您会发现:

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

我们的foo函数和bla函数各有一个符号,值字段给予了它们在.text部分中的位置。
对于所使用的符号GrCircleDraw,存在一个符号:它是未定义的,因为该函数未在该.o文件中定义,而是仍在别处找到。
.text部分(.rela.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

此地址位于foo:链接编辑器将用X1 M17 N1 X函数的地址修补该地址处的指令。

在被叫方

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

void GrCircleDraw(int x)
{

}

让我们看看它的符号表:

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

它有一个GrCircleDraw条目,用于定义其在.text节中的位置。
∮把他们联系在一起∮
因此,当链接编辑器将两个文件组合在一起时,它知道:

  • 在哪个.o文件中定义了哪些函数及其位置;
  • 其中在呼叫者的代码中它必须用被呼叫者的地址来更新。
dldeef67

dldeef673#

编译器只将extern函数的名称放入.obj文件。编译器不需要了解更多信息。
当你开始链接时,作为一个开发者,你有责任给链接器给予所有必要的目标文件和库文件。链接器会把所有这些函数安排成一个二进制文件。如果你没有指定正确的库或.obj文件,链接就会失败,并返回unresolved blah-blah
默认库通常是隐式包含的。这会使事情复杂化并产生错觉。你总是可以指定你不想要任何隐式库并显式包含所有的库。不幸的是每个系统都以自己的方式这样做。

wgmfuz8q

wgmfuz8q4#

链接通常是这样进行的:命令行被迭代,每个给定参数都是
1.如果它是目标文件则直接使用,
1.在所需范围内使用(=满足迄今为止未解决的所有引用)。
最后,为了成功链接,必须完成每个引用。链接器命令行中给出的行的顺序很重要。

相关问题