【问题标题】:Standard c++ library linking标准 c++ 库链接
【发布时间】:2014-04-20 12:22:03
【问题描述】:

我试图了解标准库何时链接到我自己的二进制文件。我写了以下内容:

#include <stdio.h>

double atof(const char*);

int main(){
    const char * v="22";
    printf("Cast result is %f", atof(v));
}

使用g++ -c main.cpp 编译成功,但是当我链接刚刚创建的目标文件时出现错误。错误描述是:

/tmp/ccWOPOS0.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `atof(char const*)'
collect2: error: ld returned 1 exit status

但我不明白为什么会导致这个错误?我认为标准 c++ 库通过 ld 链接器自动链接到我的二进制文件。包含头文件和只声明我需要显式使用的函数有什么区别。

【问题讨论】:

    标签: c++ linker standard-library


    【解决方案1】:

    作为 C++ 中的一般规则,手动声明库函数(例如 atof())是一个坏主意。

    它曾经在旧的 C 程序中很常见,但 C 没有函数重载,因此它对“几乎”正确的声明更加宽容。 (好吧,一些旧的编译器是,我真的不能代表最新的编译器)。这就是为什么我们将 C 描述为一种“弱类型”语言,而 C++ 是一种更“强类型”的语言。

    另一个复杂的问题是编译器执行“名称修改”:它们传递给链接器的名称是源名称的修改版本。 C 编译器可能执行与 C++ 编译器完全不同的名称修饰。 atof() 的标准库版本是一个 C 函数。要在 C++ 源文件中声明它,您需要将其声明为

    extern "C"
    {
        double atof(const char *);
    }
    

    或者可能

    extern "C" double atof(const char *);
    

    还有许多额外的复杂性,但这已经足够了。

    最安全的想法是只包含适当的标题。

    #include <iostream>
    #include <cstdlib>
    
    int main()
    {
        const char v[]= "22";
        std::cout << "Cast result is " << atof(v) << std::endl;
        return 0;
    }
    

    回应@DmitryFucintv 评论的额外背景

    1. 调用约定

    在调用函数时,calling convention 是关于如何在调用函数和被调用函数之间传递参数和返回值的协议。在 x86 架构上,最常见的两个是 __cdecl__stdcall,但还有许多其他的。

    考虑以下几点:

    /* -- f.c --*/
    
    int __stdcall f(int a, double b, char *c)
    {
        // do stuff
        return something;
    }
    

    /* main.c */
    
    #include <iostream>
    extern int __cdecl f(int a, double b, char *c);
    
    int main()
    {
        std::cout << f(1, 2.3, "45678") << std::endl;
        return 0;
    }
    

    在 C 程序中,这可能会编译并链接正常。函数f() 期望它的参数为 __stdcall 格式,但我们以 __cdecl 格式传递它们。结果是不确定的,但很容易导致堆栈损坏。

    因为 C++ 链接器有点麻烦,它可能会产生一个像你看到的那样的错误。大多数人都会同意这是一个更好的结果。

    2 名称修改

    Name Mangling(或名称修饰)是编译器在 object 名称中添加一些额外字符以向链接器提供一些提示的方案。 object 可能是函数或变量。允许函数重载的语言(如 C++ 和 Java)必须这样做,以便链接器能够区分同名的不同函数。 例如

    int f(int a);
    int f(double a);
    int f(const char *a, ...);
    

    【讨论】:

    • @PaulR - 当然,非常正确。我已经修正了错误。
    • 感谢您的回答。但我有些怀疑。对于纯 c++ 标准函数,我们可以在没有一些额外修饰符的情况下声明它,这是真的吗?我很有趣在这种情况下如何解决 extern "C"?
    • @DmitryFucintv - 我认为 extern "C" 是标准 C++。但带回家的信息是:使用标头而不是自己声明标准函数。为什么要自己声明函数?这有什么好处吗?
    • 仅用于教育需求。我正在尝试了解链接器的工作原理。
    • @DmitryFucintv - 我添加了一些额外的背景材料和一些有用的链接。看看这是否更清楚。
    【解决方案2】:

    这是因为 atof 具有 C 链接,而您将其编译为 C++ - 更改:

    double atof(const char*);
    

    到:

    extern "C" double atof(const char*);
    

    它应该可以工作。

    显然你通常不应该这样做,你应该使用正确的标题:

     #include <cstdlib>
    

    【讨论】:

      【解决方案3】:

      这与标准库无关。

      你遇到的问题是atof没有被定义,所以链接器没有找到它。你需要定义函数,否则不可能知道代码应该做什么。

      看起来atof 是头文件stdlib.h 中的C 函数。这段代码应该可以工作,虽然没有使用 C++ 专有函数。

      #include <stdlib.h>
      
      int main(){
          const char * v="22";
          printf("Cast result is %f", atof(v));
      }
      

      【讨论】:

        【解决方案4】:

        当您声明atof 时,您声明了一个与标准函数略有不同 的函数。您声明的函数在标准库中定义。

        不要重新声明标准函数,因为你很容易弄错,就像这里一样。您正在包含标题,并且标题为您正确声明了功能。

        【讨论】:

          猜你喜欢
          • 2011-06-16
          • 2013-09-19
          • 1970-01-01
          • 2021-10-27
          • 1970-01-01
          • 1970-01-01
          • 2018-02-10
          • 2013-05-22
          • 2021-06-25
          相关资源
          最近更新 更多