2

I have the following source code:

  • foo.h

    void foo();
    
  • foo.cpp

    #include "foo.h"
    #include <iostream>
    
    void foo(){
        std::cout << "This is foo" << std::endl;
    }
    
  • main.cpp:

    #include "foo.h"
    
    int main(){
        foo();
    
        return 0;
    }
    

Then I run the following command to produce both static and shared versions of foo.cpp:

g++ -c -fPIC foo.cpp
ar rvs libfoo.a foo.o
g++ -shared -o libfoo.so foo.o
g++ -c main.cpp

If I try to generate an executable using the static libfoo.a and main.o, the order of the object files and library file matters:

g++ main.o libfoo.a -o main  # works
g++ libfoo.a main.o -o main  # fails with undefined references to `foo()`

As explained in this post, the order matters.

However, if I try to build an executable using the shared libfoo.so and main.o, the order of object file and library file doesn't matter any more:

g++ main.o libfoo.so -o main   # works
g++ libfoo.so main.o -o main   # also works without errors

Does this mean that the order is not important when we link object file and shared libraries? Or this is a just a special case or some coincidence I am not aware of?

The above code and command is tested on the following platform:

  • Linux: CentOS 7.4
  • gcc: 7.3.1 or 4.8.5 (both tested)
jdhao
  • 24,001
  • 18
  • 134
  • 273
  • 2
    Linking against a static library only pulls into the target for the symbols that are unresolved. (A static library is a container file that holds object files.) A shared library contains the entire library, even if your code only needs 1 symbol from that shared library. Shared libraries are symbols resolved at load time, not link time. Each has pros and cons; for the applications I write static libraries are a big win, and shared libraries are a big pain. – Eljay Jul 03 '21 at 11:48

2 Answers2

1

As explained in this post, the order matters.

A more detailed explanation is here.

The order matters for traditional UNIX linkers, but recently LLD decided to break with tradition, and record availability of symbols in archive libraries even when they aren't immediately referenced.

Currently on Linux three different linkers are available: the original BFD linker, Gold linker (circa 2008), and LLD (circa 2019).

Using "correct" order succeeds with all three (naturally).

Using the "wrong" order: g++ libfoo.a main.o fails with BFD and Gold, but succeeds with LLD:

g++ libfoo.a main.o -fuse-ld=lld && echo $?
0

However, if I try to build an executable using the shared libfoo.so and main.o, the order of object file and library file doesn't matter any more

The reason it doesn't matter is that linkers treat .so similarly to how they treat .o. In particular, there is no need to "pull out parts" of .so -- you get the entire .so (just as you would1 if you used foo.o)

So when you link g++ libfoo.so main.o, this link is almost equivalent to using g++ foo.o main.o, and the order in the former command doesn't matter for the same reason it doesn't matter in the latter command.


1Building with -ffunction-sections and using --gc-sections notwithstanding.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Thanks for this info! From comment in [this post](https://stackoverflow.com/a/41222018/6064933), we need gcc 9.3.0+ to use option `-fuse-ld=lld`. – jdhao Jul 06 '21 at 05:24
0

Because no strong symbol name resolution is required in second case. Symbols in shared object are looked up and resolved during ELF interpretation, i.e. when you run your program.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • Is there any authoritative documentation on this behaviour? I can seem to find the right doc on the `ld` manual. – jdhao Jul 03 '21 at 11:31
  • @jdhao rather look for ld.so man , but that would be linux-specific.Each OS implements it differently, though ELF-using ones are somewhat similar. A general idea is that symbols stay unresolved until program is run and stay referenced inside some specially marked table. The.so file on ELF systems is needed as a source for that table. PEXE systems use import library instead, which is separate from .DLL file. – Swift - Friday Pie Jul 03 '21 at 11:42
  • If symbols from the shared library are resolved at run time. Why do we need to link against it when we build the executable. `g++ main.o -o main` won't compile. – jdhao Jul 03 '21 at 12:23
  • @jdhao because you won't have the table. and if you "link" .so library, you link entirety of it, not just symbols used. This would allow to get pointers to them at run-time as well, see dlopen/dlsym – Swift - Friday Pie Jul 03 '21 at 12:50
  • The first statement is patently wrong. The second statement reads like complete gibberish to me. – Employed Russian Jul 03 '21 at 16:38
  • @EmployedRussian so, maybe you'd like to write your answer if you know how to split hairs there? – Swift - Friday Pie Jul 03 '21 at 19:12