【问题标题】:How does the linker determine which function to link to?链接器如何确定要链接到哪个函数?
【发布时间】:2015-01-07 12:25:47
【问题描述】:

下面的代码是我看到的问题的简化版本;基本上外部函数testprint() 最终调用了test_xprintf.cpp 中定义的printf(),而不是标准的printf()

(是的,代码看起来很奇怪,但它是为了代表问题,所以它本身不一定有意义。)

为什么链接器链接到test_xprintf 中定义的printf()?这是预期的行为还是依赖于工具?

//
// test_xprintf.cpp
//

#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include "test_dbgprintf.h"

/**
 *
 * There are 3 files in total:
 * - test_xprintf.cpp
 * - test_dbgprintf.h
 * - test_dbgprintf.cpp
 *
 * Create a static C lib from test_dbgprintf.c and link with test_xprintf.cpp
 *
 * gcc -Wall -g -c -o test_dbgprintf.o test_dbgprintf.c && 
 * ar -rcs  libtest_dbgprintf.a test_dbgprintf.o &&
 * g++ -Wall test_xprintf.cpp -L. -ltest_dbgprintf -I.
 */

extern "C" int printf(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    vprintf(format, ap);
    va_end(ap);

    return -1;
}

int main()
{
    // testprint() is a shell function which simply calls printf. 
    // If the printf function above is called, the return value will be -1. 
    int ret = testprint(4);
    std::cout << "Ret value is " << ret << std::endl;
    return ret;
}

//
// test_dbgprintf.h
//

#ifndef TEST_DBGPRINTF_H
#define TEST_DBGPRINTF_H

#if defined (__cplusplus)
extern "C" {
#endif

int testprint(int num);

#if defined (__cplusplus)
}
#endif


#endif

//
// test_dbgprintf.c
//

#include <stdio.h>

int testprint(int num)
{
    // By right this should be calling the std printf but it is linked to the printf in test_printf.cpp instead.
    return printf("This is called from testprint %d\n", num);
}

【问题讨论】:

  • 你为什么要在 C++ 中尝试这种方法?只需使用命名空间或类来阐明您的意思是哪个printf
  • 这不是自愿的,只是代码的组合方式。现在我更想知道为什么会发生而不是修复。
  • test_xprintf.cppprintf() 的签名与 std 库中 printf() 的签名匹配,并且它们都在您的全局命名空间中,因为您使用的是 C 而不是 C++。因此,链接器很可能只是在找到 std 库的 OBJ 之前找到 test_xprintf.cpp 的 OBJ。这是一个先到先得的问题。这正是 C++ 命名空间帮助解决的那种冲突。
  • 在标准 C 或标准 C++ 中,这是未定义的行为(即标准不涵盖此处发生的情况)。您使用的是哪个链接器?
  • 我正在使用 gnu 工具链。是的,我也这么认为;很高兴得到确认。

标签: c++ c linker


【解决方案1】:

这是 GNU 链接器的已知行为。解析符号时,它只会检测 .o 之间的多个定义;如果在 .o 中没有找到定义,它只会求助于库;然后它会在第一次匹配后停止搜索。

这是默认行为。您可以使用 --whole-archive 覆盖它,但这可能会使您的生成模块膨胀。

【讨论】:

    【解决方案2】:

    好的,我认为在阅读了这个非常有用的blog 之后,我找到了一个合理的解释。

    它取决于链接顺序。这是我掩盖的东西,但看看我链接库的方式:

    g++ -Wall test_xprintf.cpp -L. -ltest_dbgprintf -I.
    

    将其扩展为两个步骤:

    g++ -Wall -g -c -o test_xprintf.o test_xprintf.cpp
    g++ -L. test_xprintf.o -ltest_dbgprintf -I.
    

    我认为发生了什么是链接器:

    • test_xprintf 中的第一个导出符号 printf()
    • 遇到库test_xprintf时,发现未定义符号printf
    • 查找当前导出符号列表,找到printf() 并愉快地将它们链接在一起。

    libC 是最后链接的,我相信这解释了为什么它看不到它。

    根据解释,我相信这种行为是预期的。

    【讨论】:

      【解决方案3】:

      您额外的printf 符号(作为C 函数可见)使链接器感到困惑。很有可能,您还应该看到有关多重定义符号的链接器警告。

      该标准甚至不考虑这种意义上的多重定义符号(更不用说翻译单元之外的大部分内容了)。

      典型的链接器会绑定第一个匹配的符号(在某种意义上的“第一个”),并对任何匹配的附加项发出警告。

      总结一下:这种行为完全取决于链接器的实现。

      【讨论】:

      • 没有看到任何警告。也许我没有传递适当的标志?
      【解决方案4】:

      在某些编译器中,std 库被视为具有弱链接。 您定义的符号始终具有强链接,除非使用某些属性或类似属性另行定义,但默认情况下它们是“强”链接的。

      这意味着您的定义符号和标准符号之间的任何冲突符号都将被解析为您提供的符号,即“强”符号。

      当我遇到这个问题时(使用 CodeWarrior 的 ColdFire 编译器),我总是收到链接器警告:

      Symbol _sprintf multiply defined in printf.c() and libc.a(printf.o       )
      Ignoring the definition in libc.a(printf.o       )
      

      您可能会看到类似的警告。

      为什么有时在标准库中需要这种链接?好吧,在嵌入式系统中,也许有些函数对于处理器来说太重了,无法执行,一个例子是 printf 函数(不是 printf 本身,因为它只是一个包装器,我认为它是 vsprintf 或类似的,但我没有不记得是哪一个),很长(有时它几乎吞噬了我整个记忆的 1/4),所以我不得不提供我自己的简化版本。

      我不知道这是标准的,还是只是依赖于链接器。

      【讨论】:

      • 我没有看到任何使用 gnu 链接器的警告。然后也许我需要传递某些标志。
      • 我不知道在 GNU 链接器中。但你可以试试这个:stackoverflow.com/questions/13993966/…
      猜你喜欢
      • 2012-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-23
      • 1970-01-01
      • 2011-07-08
      • 2018-02-09
      相关资源
      最近更新 更多