C++ 链接器如何知道外部函数的定义在哪里?

C++ 链接器如何知道外部函数的定义在哪里?,c++,c,extern,keil,C++,C,Extern,Keil,我读了几篇文章,得出的结论是extern告诉编译器“这个函数存在,但它的代码在其他地方。不要惊慌。”但是链接器如何知道函数的定义位置呢 我的情况是:- 我正在制作Keil uvision 4。有一个头文件grlib.h,主函数在grlib_demo.c中(包括grlib.h)。现在,有一个函数GrCircleDraw()在Circle.c中定义,在grlib_demo.c中调用,还有一个语句 外部无效GrCircleDraw(所有参数) 在grlib.h。我的问题是链接器如何知道GrCircle

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

我的情况是:- 我正在制作Keil uvision 4。有一个头文件grlib.h,主函数在grlib_demo.c中(包括grlib.h)。现在,有一个函数GrCircleDraw()在Circle.c中定义,在grlib_demo.c中调用,还有一个语句

外部无效GrCircleDraw(所有参数)

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


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

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

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

开始链接时,作为开发人员,您有责任向链接器提供所有必要的对象文件和库文件。链接器将把所有这些函数排列成一个二进制文件。如果没有指定正确的库或
.obj
文件,链接将失败,出现
未解析的废话


默认库通常隐式包含。这会使事情复杂化并产生幻觉。您可以始终指定不需要任何隐式库,并显式包含所有内容。不幸的是,每个系统都以自己的方式进行链接。

链接通常以这种方式进行:命令行被迭代,每个参数都被指定

  • 如果是对象文件,则直接使用
  • 在需要的范围内使用(=满足迄今为止未解决的所有参考)

  • 最后,为了成功链接,必须完成每个引用。链接器命令行中给出的行顺序很重要。

    在中编译.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;
    }
    
    与:

    (我使用的是我系统的本机编译器,但在交叉编译到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
    中:链接编辑器将使用
    GrCircleDraw
    函数的地址修补此地址处的指令

    在被叫方 现在让我们自己编译一个
    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 符号表“.symtab”包含9个条目: Num:值大小类型绑定到Ndx名称 [...] 8:0000000000000000 9函数全局默认值1 GrCircleDraw 它有一个用于
    GrCircleDraw
    的条目,用于定义其
    .text
    部分中的位置

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

    • 在哪个
      .o
      文件中定义了哪些函数及其位置
    • 其中,在调用方的代码中,它必须使用被调用方的地址进行更新

    欢迎来到linkerAt的魔力一级,编译器不知道;查找函数是链接器的工作。链接器知道,因为你告诉它在哪里可以找到它——或者在命令行的对象文件中,或者在库中。@jozefg你是说黑魔法,对吧?废话,我写错了编译器,当然它必须是链接器。但是,链接器必须知道以某种形式定义的位置。在my code Circle.c中(其中包括未添加任何位置的定义)。链接器怎么可能仍然找到它?链接器必须找到
    GrCircleDraw
    的至少一个定义。通常,它会忽略第一个定义之后的任何额外定义,但如果定义在两个对象文件中,或者如果您正在链接一个静态库,并且库中包含第二个定义的对象文件也需要满足某些其他符号,则它会反对;它会抱怨双重定义。@JonathanLeffler没有第二个定义。我的问题是链接器是如何找到一个定义的。@AnkitGupta:那么你告诉我们:(1)有一个
    circle.c
    ,但它没有被编译;(2)
    GrCircleDraw
    的唯一定义是在
    circle.c
    中;(3)您的程序编译、链接和运行成功。我不相信所有这些都是真的。特别是,我怀疑(2),并且确实有另一个定义
    GrCircleDraw
    ,可能是在您链接的库文件中。您的链接器应该附带一些实用程序来帮助您完成类似的工作。我认为VisualC++工具是名为<代码> DimpBi.exe < /C>但你没有说你使用的是哪一个编译器工具链。另一种方法是让你的链接器创建一个映射文件输出,它显示了很多关于l
    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