Why doesn't the linker complain of duplicate symbols?

前端 未结 1 1256
失恋的感觉
失恋的感觉 2021-01-05 20:26

I have a dummy.hpp

#ifndef DUMMY
#define DUMMY
void dummy();
#endif

and a dummy.cpp

#include 
void dummy()         


        
相关标签:
1条回答
  • 2021-01-05 21:10

    There's no error in either Scenario 1 (dual static libraries) or Scenario 2 (static and shared libraries) because the linker takes the first object file from a static library, or the first shared library, that it encounters that provides a definition of a symbol it has not yet got a definition for. It simply ignores any later definitions of the same symbol because it already has a good one. In general, the linker only takes what it needs from a library. With static libraries, that's strictly true. With shared libraries, all the symbols in the shared library are available if it satisfied any missing symbol; with some linkers, the symbols of the shared library may be available regardless, but other versions only record the use a shared library if that shared library provides at least one definition.

    It's also why you need to link libraries after object files. You could add dummy.o to your linking commands and as long as that appears before the libraries, there'll be no trouble. Add the dummy.o file after libraries and you'll get doubly-defined symbol errors.

    The only time you run into problems with this double definitions is if there's an object file in Library 1 that defines both dummy and extra, and there's an object file in Library 2 that defines both dummy and alternative, and the code needs the definitions of both extra and alternative — then you have duplicate definitions of dummy that cause trouble. Indeed, the object files could be in a single library and would cause trouble.

    Consider:

    /* file1.h */
    extern void dummy();
    extern int extra(int);
    
    /* file1.cpp */
    #include "file1.h"
    #include <iostream>
    void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
    int extra(int i) { return i + 37; }
    
    /* file2.h */
    extern void dummy();
    extern int alternative(int);
    
    /* file2.cpp */
    #include "file2.h"
    #include <iostream>
    void dummy() { std::cerr << "dummy() from " << __FILE__ << '\n'; }
    int alternative(int i) { return -i; }
    
    /* main.cpp */
    #include "file1.h"
    #include "file2.h"
    int main()
    {
        return extra(alternative(54));
    }
    

    You won't be able to link the object files from the three source files shown because of the double-definition of dummy, even though the main code does not call dummy().

    Regarding:

    The loader seems always to choose dynamic libs and not compiled in the .o files.

    No; the linker always attempts to load object files unconditionally. It scans libraries as it encounters them on the command line, collecting definitions it needs. If the object files precede the libraries, there's not a problem unless two of the object files define the same symbol (does 'one definition rule' ring any bells?). If some of the object files follow libaries, you can run into conflicts if libraries define symbols that the later object files define. Note that when it starts out, the linker is looking for a definition of main. It collects the defined symbols and referenced symbols from each object file it is told about, and keeps adding code (from libraries) until all the referenced symbols are defined.

    Does it means the same symbol is always loaded from .so file, if it is both in .a and .so file?

    No; it depends which was encountered first. If the .a was encountered first, the .o file is effectively copied from the library into the executable (and the symbol in the shared library is ignored because there's already a definition for it in the executable). If the .so was encountered first, the definition in the .a is ignored because the linker is no longer looking for a definition of that symbol — it's already got one.

    Does it mean that symbols in static symbol table in a static library are never in conflict with those in dynamic symbol table in .so file?

    You can have conflicts, but the first definition encountered resolves the symbol for the linker. It only runs into conflicts if the code that satisfies the reference causes a conflict by defining other symbols that are needed.

    If I link 2 shared libs, can I get conflicts and the link phase failed?

    As I noted in a comment:

    My immediate reaction is "Yes, you can". It would depend on the content of the two shared libraries, but you could run into problems, I believe. […cogitation…] How would you show this problem? … It's not as easy as it seems at first sight. What is required to demonstrate such a problem? … Or am I overthinking this? … […time to go play with some sample code…]

    After some experimentation, my provisional, empirical answer is "No, you can't" (or "No, on at least some systems, you don't run into a conflict"). I'm glad I prevaricated.

    Taking the code shown above (2 headers, 3 source files), and running with GCC 5.3.0 on Mac OS X 10.10.5 (Yosemite), I can run:

    $ g++ -O -c main.cpp
    $ g++ -O -c file1.cpp
    $ g++ -O -c file2.cpp
    $ g++ -shared -o libfile2.so file2.o
    $ g++ -shared -o libfile1.so file1.o
    $ g++ -o test2 main.o -L. -lfile1 -lfile2
    $ ./test2
    $ echo $?
    239
    $ otool -L test2
    test2:
        libfile2.so (compatibility version 0.0.0, current version 0.0.0)
        libfile1.so (compatibility version 0.0.0, current version 0.0.0)
        /opt/gcc/v5.3.0/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.21.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)
        /opt/gcc/v5.3.0/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
    $
    

    It is aconventional to use .so as the extension on Mac OS X (it's usually .dylib), but it seems to work.

    Then I revised the code in the .cpp files so that extra() calls dummy() before the return, and so does alternative() and main(). After recompiling and rebuilding the shared libraries, I ran the programs. The first line of output is from the dummy() called by main(). Then you get the other two lines produced by alternative() and extra() in that order because the calling sequence for return extra(alternative(54)); demands that.

    $ g++ -o test2 main.o -L. -lfile1 -lfile2
    $ ./test2
    dummy() from file1.cpp
    dummy() from file2.cpp
    dummy() from file1.cpp
    $ g++ -o test2 main.o -L. -lfile2 -lfile1
    $ ./test2
    dummy() from file2.cpp
    dummy() from file2.cpp
    dummy() from file1.cpp
    $
    

    Note that the function called by main() is the first one that appears in the libraries it is linked with. But (on Mac OS X 10.10.5 at least) the linker does not run into a conflict. Note, though, that the code in each shared object calls 'its own' version of dummy() — there is disagreement between the two shared libraries about which function is dummy(). (It would be interesting to have the dummy() function in separate object files in the shared libraries; then which version of dummy() gets called?) But in the extremely simple scenario shown, the main() function manages to call just one of the dummy() functions. (Note that I'd not be surprised to find differences between platforms for this behaviour. I've identified where I tested the code. Please let me know if you find different behaviour on some platform.)

    0 讨论(0)
提交回复
热议问题