Here is a very simplified illustration of the linker behavior that puzzles you:
main.c
extern void foo(void); int main(void) { foo(); return 0; }
foo.c
#include <stdio.h> void foo(void) { puts(__func__); }
bar.c
#include <stdio.h> extern void do_bar(void); void bar(void) { do_bar(); }
do_bar.c
#include <stdio.h> void do_bar(void) { puts(__func__); }
Compile all these source files into object files:
$ gcc -Wall -c main.c foo.c bar.c do_bar.c
Now we will try to link the program, for example:
$ gcc -o prog main.o foo.o bar.o bar.o: In function `bar': bar.c:(.text+0x5): undefined reference to `do_bar'
The undefined do_bar function is referenced only in the definition of bar , and bar not mentioned in programs at all. Why then communication failure?
Simply put, this link failed because we told the linker to link bar.o into a program; so it was; and bar.o contains the definition of bar , which refers to do_bar , which is not defined in the connection. bar no, but do_bar is - from bar , which is connected in the program.
By default, the linker requires that any character referenced by the program link is defined in the binding. If we force it to bind the definition from bar , then it will require the definition of do_bar , because without the definition of do_bar in fact, it did not get the definition of bar . This is if the definition of links is bar , it is not a question of whether we need to bind it, and then allow undefined links to do_bar if the answer is No.
Communication failure can be fixed with:
$ gcc -o prog main.o foo.o bar.o do_bar.o $ ./prog foo
Now in this illustration, the bar.o link in the program is simply free. We can also successfully link by simply not telling the linker the bar.o link.
gcc -o prog main.o foo.o $ ./prog foo
bar.o and do_bar.o are superfluous for main execution, but the program can be connected only with both, or neither
But suppose foo and bar defined in the same file?
They can be defined in the same object file, foobar.o :
ld -r -o foobar.o foo.o bar.o
And then:
$ gcc -o prog main.o foobar.o foobar.o: In function `bar': (.text+0x18): undefined reference to `do_bar' collect2: error: ld returned 1 exit status
Now the linker cannot bind the definition of foo without binding the definition of bar . So again, we need to bind the definition of do_bar :
$ gcc -o prog main.o foobar.o do_bar.o $ ./prog foo
A related prog contains the definitions of foo , bar and do_bar :
$ nm prog | grep -e foo -e bar 000000000000065d T bar 0000000000000669 T do_bar 000000000000064a T foo
( T = specific function symbol).
Equally, foo and bar can be defined in the same shared library:
$ gcc -Wall -fPIC -c foo.c bar.c $ gcc -shared -o libfoobar.so foo.o bar.o
and then this binding:
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd) ./libfoobar.so: undefined reference to `do_bar' collect2: error: ld returned 1 exit status
performed the same way as before, and fixed in the same way:
$ gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd) $ ./prog foo
When we link the shared library libfoobar.so , and not the object file foobar.o , our prog has a different symbol table:
$ nm prog | grep -e foo -e bar 00000000000007aa T do_bar U foo
This time, prog contains no definitions of either foo or bar . This contains the undefined ( U ) link to foo , since it calls foo and of course this link will now be executed at runtime by definition in libfoobar.so . There is even an undefined reference to bar , and it should not be, because the program never calls bar .
But still, prog contains a do_bar definition, which is now not specified from all functions in the symbol table.
This repeats your own SSCCE, but in a less confusing way. In your case:
The libsub.a(shared2.o) object file libsub.a(shared2.o) associated with the program for defining definitions for func2a and func2b .
These definitions must be found and linked because they are referenced respectively by the definitions of Client_func2a and Client_func2b , which are defined in libcshared.so .
libcshared.so must be associated with providing the definition of Client_func1a .
The definition of Client_func1a must be found and linked because it refers to the definition of func1a .
And func1a is called main .
This is why we see:
$ nm main | grep func2 U Client_func2a U Client_func2b 00000000004009f7 T func2a 0000000000400a30 T func2b
in the symbol table of your program.
It is not at all unusual that definitions should be associated with a program for functions that it does not call. Usually this happens as we saw: a connection, recursively resolving character references starting with main , finds that this requires a definition from f , which it can only get by linking some object file file.o and file.o it also binds the definition of g , which is never called.
Which is rather strange, it ends up with a program like your main , and like my latest version of prog , which contains a definition of an unclaimed function (e.g. do_bar ) that is related to resolving a link from the definition of another unclaimed function (e.g. bar ), which is not defined in the program. Even if redundant definitions of functions exist, we can usually associate them with one or more object files in the link into which the first redundant definitions are inserted along with some necessary corrections.
This oddity arises in the case, for example:
gcc -o prog main.o do_bar.o -L. -lfoobar -Wl,-rpath=$(pwd)
since the first definition of the redundant function that must be associated ( bar ) is provided by linking the shared library libfoobar.so , while the definition of the do_bar that bar requires is not in this shared library or in any other shared library, but in the object file.
The bar definition provided by libfoobar.so will remain there when the program is associated with this shared library. He will not be physically associated with the program. This is the nature of dynamic communication. But any object file required by linkage - whether it is a standalone object file like do_bar.o or one that extracts a linker from an archive like libsub.a(shared2.o) - can only be physically connected to the program. So the redundant do_bar appears in the prog character table. But the excess bar , which explains why do_bar is, does not exist. It is located in the libfoobar.so symbol libfoobar.so .
When you find dead code in your program, you may need to make the linker smarter. This can usually be wiser, due to some extra effort. You need to ask him about garbage collection, and before that you need to ask the compiler to prepare the path by creating data partitions and functional sections in object files. See How to remove unused C / C ++ characters using GCC and ld? and the answer
But this way of trimming dead code will not work in the unusual case when dead code is linked in a program to satisfy redundant links from a shared library required by the link. The compiler can only recursively garbage collect unused sections from those that it outputs to the program, and it only outputs sections that are entered from object files, and not from shared libraries that should be dynamically linked.
The correct way to avoid dead code in main and my prog is not to make this peculiar kind of connection, in which the shared library will contain undefined links that the program does not call, but which should be resolved by linking the dead object code in your program.
Instead, when you create a shared library, either do not leave undefined links in it, or leave only undefined links that must be satisfied with its own dynamic dependencies.
So the correct way to create my libfoobar.so :
$ gcc -shared -o libfoobar.so foo.o bar.o do_bar.o
This gives me a shared library with the API:
void foo(void); void bar(void);
for those who want them or both, and undefined links. then I create my program, which is the client only foo :
$ gcc -o prog main.o -L. -lfoobar -Wl,-rpath=$(pwd) $ ./prog foo
And it does not contain dead code:
$ nm prog | grep -e foo -e bar U foo
Similarly, if you create your libshared.so without undefined links, for example:
$ gcc -c -fPIC shared2.c shared1.c $ ar -crs libsub.a shared1.o shared2.o $ gcc -shared -o libcshared.so cshared1.o cshared2.o -L. -lsub
and then a link to your program:
$ gcc -o main main.c libcmain.so libcshared.so
it will also not have dead code:
$ nm main | grep func U func1a
If you donโt like the fact that libsub.a(shared1.o) and libsub.a(shared2.o) physically related to this solution, libcshared.so , then take another orthodox approach to linking the shared library: leave all the func* functions undefined in libcshared.so : make libsub also a shared library, which is then a dynamic dependency of libcshared.so .