Search code examples
c++linkerstandard-library

Standard c++ library linking


I'm trying to understand when does standard library linking to my own binary. I've written the following:

#include <stdio.h>

double atof(const char*);

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

It's compiling successful with g++ -c main.cpp, but when I'm linking just created object file I've an error. Error descriptio is:

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

But I don't understand why this error is caused? I think that the standard c++ library automatically linked to my binary by the ld linker. What is the difference between the including header files and just declaring a functions which I need to use explicitly .


Solution

  • As a general rule in C++, it is a bad idea to manually declare library functions such as atof().

    It used to be common in old C programs, but C doesn't have function overloading so it is more forgiving about "almost" correct declarations. (Well some of the old compilers were, I can't really speak for the newest ones). That is why we describe C as a "weakly typed" language, while C++ is a more "strongly typed" language.

    An additional complication is that the compilers perform "name mangling": the name they pass to the linker is a modified version of the source name. The C compiler may perform quite different name mangling from the C++ compiler. The standard lib version of atof() is a C function. To declare it in a C++ source file you need to declare it as

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

    or possibly

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

    There are many additional complexities, but that is enough to go on with.

    Safest idea is to just include the appropriate headers.

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

    Extra background in response to comment by @DmitryFucintv

    1. Calling conventions

    When calling a function, a calling convention is an agreement on how parameters and return values are passed between the calling function and the called function. On x86 architecture, the two most common are __cdecl and __stdcall, but a number of others exist.

    Consider the following:

    /* -- 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;
    }
    

    In a C program, this will probably compile and link OK. The function f() is expecting its args in __stdcall format, but we pass them in __cdecl format. The result is indeterminate, but could easily lead to stack corruption.

    Because the C++ linker is a bit fussier, it will probably generate an error like the one you saw. Most would agree that is a better outcome.

    2 Name Mangling

    Name Mangling (or name decoration) is a scheme where the compiler adds some extra characters to the object name to give some hints to the linker. An object might be a function or a variable. Languages that permit function overloading (like C++ and Java) must do something like this so that the linker can tell the difference between different functions with the same name. e.g.

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