37

In the following example, the program should print "foo called\n":

// foo.c
#include <stdio.h>

__attribute__((constructor)) void foo()
{
    printf("foo called\n");
}

// main.c
int main()
{
    return 0;
}

If the program is compiled like this, it works:

gcc -o test main.c foo.c

However, if foo.c is compiled into a static library, the program prints nothing.

gcc -c main.c
gcc -c foo.c
as rcs foo.a foo.o
gcc -o test foo.a main.o

Why does this happen?

Neuron
  • 5,141
  • 5
  • 38
  • 59
Jay Conrod
  • 28,943
  • 19
  • 98
  • 110
  • Why the downvotes? Is something incorrect? – Jay Conrod Jul 29 '09 at 19:41
  • Not sure (wasn't me!) but perhaps someone took exception to you answering your own question so quickly? – DaveR Jul 29 '09 at 20:52
  • 4
    Hmm, I just wanted to add a useful reference to the site for a non-obvious problem. The FAQ indicates answering one's own question is a good thing (it's in the first section actually). – Jay Conrod Jul 29 '09 at 22:32

2 Answers2

19

The linker does not include the code in foo.a in the final program because nothing in main.o references it. If main.c is rewritten as follows, the program will work:

//main.c

void foo();

int main()
{
    void (*f)() = foo;
    return 0;
}

Also, when compiling with a static library, the order of the arguments to gcc (or the linker) is significant: the library must come after the objects that reference it.

gcc -o test main.o foo.a
Jay Conrod
  • 28,943
  • 19
  • 98
  • 110
9

As it was stated, unreferenced symbols from archive does not make it to the output binary, because linker discards them by default.

To override this behaviour when linking with static library, --whole-archive/--no-whole-archive options for the linker may be used, like this:

gcc -c main.c
gcc -c foo.c
ar rcs foo.a foo.o
gcc -o test -Wl,--whole-archive foo.a -Wl,--no-whole-archive main.o

This may lead to bloated binary, because all symbols from foo.a will be included by the linker to the output, but sometimes it is justified.

yerden
  • 93
  • 1
  • 4
  • One (verbose) workaround for that bloat is to split the library. Make one `.c` file that contains nothing but an `__attribute__((constructor))` function that just calls an implementation and build a library from that. Then put everything else (including that implementation) in a second library. The linker then gets to GC as normal. Also that allows the implementation to be called manually if the other way doesn't work (e.g. if you need to force a specific runtime order or suppress it all together in some invocations). – BCS Aug 05 '23 at 18:17