4

If we have a function foo() which has same prototype in two different libraries with different implementation and if we include only single header file (which has declaration of function), what happens if we try to compile or execute the program at compile or runtime ?

  • Did you try that? – Basile Starynkevitch Jan 05 '19 at 07:48
  • 3
    The problem will appear at link time, linker will tell you that there is a conflict and you'll have to choose one of the two libraries... For dynamically loaded libraries, the problem could be very different. – Jean-Baptiste Yunès Jan 05 '19 at 07:50
  • @Jean-BaptisteYunès: Having the same symbol in two libraries generally does not produce an error. Object modules are linked from libraries on demand, not always. That is, a linker typically links in an object module from a library only if it provides a definition for a symbol referenced by a previous module and not yet defined. So a second definition of a symbol will not be linked in (and will not cause an error) unless it is in a module that also defines another symbol that is still needed. – Eric Postpischil Jan 05 '19 at 12:33
  • @EricPostpischil this highly depends on the linker... – Jean-Baptiste Yunès Jan 06 '19 at 13:19
  • @Jean-BaptisteYunès: Highly? It is very common linker behavior. One of the main features of linking with a library is that only needed modules are linked in, so the executable is not bloated with unnecessary modules. A natural consequence of that is that a module is linked in only if it provides a definition that is needed, so a later module that provides a definition of the same symbol is not linked in. – Eric Postpischil Jan 06 '19 at 14:25

2 Answers2

7

Per your tags, you are asking about the linkage of static libraries. The linker doesn't know or care what source language(s) its input files were compiled from. The C language is immaterial to the question, but I'll use C for illustration.

Read the Stackoverflow tag Wiki about static libraries and it will explain that linking your program with a static library is exactly the same as linking your program with 0 or more of the object files that are archived in the static library - namely the 0 or more object files that the linker needs to provide definitions for otherwise unresolved symbol-references in the program.

Once the linker has found an object file p.o file in a static library libx.a that provides it with a definition for some symbol foo that the program refers to, it will link the object file libx.a(p.o) into the program to resolve foo. It will not try to find any other definition of foo in any other object file q.o file in any other static library liby.a that comes after libx.a in the linkage.

So if there is any other object file q.o in any other static library liby.a that comes later in the linkage than libx.a that also contains a definition of foo, that object file liby.a(q.o) will not even be linked into the program unless the linker needs it provide a definition of some other symbol bar that the program refers to. Assuming that is not the case, liby.a(q.o) might as well not exist, for linkage purposes.

The linker will not link multiple definitions of the same symbol from libx.a(p.o) and liby.a(q.o) if it does not need to. It will link the first object file, libx.a(p.o), that defines foo into the program, and then it is done with defining foo.

Here's an illustration of that:

main.c

extern void foo(void);
extern void bar(void);

int main(void)
{
    foo();
    bar();
    return 0;
}

p.c

#include <stdio.h>

void foo(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

q.c

#include <stdio.h>

void foo(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

r.c

#include <stdio.h>

void bar(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

Function foo is defined in p.c and also in q.c.

Compile all the .c files to .o files:

$ gcc -c main.c p.c q.c r.c

Create three static libraries, one from each of p.o, q.o, r.o:

$ ar rcs libx.a p.o
$ ar rcs liby.a q.o
$ ar rcs libz.a r.o

Then link a program, inputting libx.a before liby.a:

$ gcc -o prog main.o libz.a libx.a liby.a -Wl,-trace-symbol=foo
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: libx.a(p.o): definition of foo

The diagnostic linkage option -Wl,-trace-symbol=foo asks the linker to tell us the names of the files that are linked into prog where it finds unresolved references to foo and also the name of the file where foo is defined. You see that foo is referenced in main.o and the definition provided by libx.a(p.o) is linked. The other definition of foo in liby.a(q.o) is not linked. This linkage is exactly the same as

gcc -o prog main.o r.o p.o

which contains only the definition of foo from p.o, as the program shows:

$ ./prog
foo from p.c
bar from r.c

Now relink prog, this time with liby.a before libx.a:

$ gcc -o prog main.o libz.a liby.a libx.a -Wl,-trace-symbol=foo
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: liby.a(q.o): definition of foo

This time, the definition of foo was linked from liby.a(q.o). This linkage is exactly the same as:

gcc -o prog main.o r.o q.o

which contains only the definition of foo from q.o, as the program shows:

$ ./prog
foo from q.c
bar from r.c

The linker does not care how many definitions of foo you offer it in different object files in different static libraries. It only cares that if foo is referenced in the program, then foo is defined, exactly once, by the files that it links into the program.

If you compel the linker to link files into the program that contain more than one definition of foo, then by default and normally the linker will give you a multiple definition error and the linkage will fail, because there cannot be more than one definition of foo in a program. Here's an illustration of that:

qr.c

#include <stdio.h>

void foo(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

void bar(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

Compile that file:

$ gcc -c qr.c

Archive qr.o in a new static library:

$ ar rcs libyz.a qr.o

The object file libyz.a(qr.o) defines both foo and bar. So we can link our program like:

$ gcc -o prog main.o libyz.a -Wl,-trace-symbol=foo,-trace-symbol=bar
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: main.o: reference to bar
/usr/bin/ld: libyz.a(qr.o): definition of foo
/usr/bin/ld: libyz.a(qr.o): definition of bar

And it runs like:

$ ./prog
foo from qr.c
bar from qr.c

But if we attempt to link it like:

$ gcc -o prog main.o libx.a libyz.a -Wl,-trace-symbol=foo,-trace-symbol=bar
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: main.o: reference to bar
/usr/bin/ld: libx.a(p.o): definition of foo
/usr/bin/ld: libyz.a(qr.o): in function `foo':
qr.c:(.text+0x0): multiple definition of `foo'; libx.a(p.o):p.c:(.text+0x0): first defined here
/usr/bin/ld: libyz.a(qr.o): definition of bar
collect2: error: ld returned 1 exit status

there is a multiple definition of foo. That is because:

  • The linker needed a definition of foo and found the first one in libx.a(p.o); so it linked that file into the program. It won't search for any other one.
  • The linker needed a definition of bar and found the first one in libyz.a(qr.o); so it linked that file into the program. It won't search for any other one.
  • But libyz.a(qr.o) contains another definition of foo as well as a definition of bar. So now two definitions of foo have been linked, and that is an error.

I said that you'll get a multiple definition error by default if you make the linker attempt to link into the program more than one file that defines a symbol.

But you can avoid that by telling the linker than the symbol is weakly defined, provided that your linker understands this concept (as the GNU and Apple linkers do).

GCC compilers support a non-standard language extension, the __attribute__ syntax that you can use to communicate to the linker that a symbol definition is weak. Here's an illustration of that:

qr.c (2)

#include <stdio.h>

void __attribute__((weak)) foo(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

void bar(void)
{
    printf("%s %s %s\n",__func__,"from",__FILE__);
}

Recompile:

$ gcc -c qr.c

Delete libyz.a and recreate it:

$ rm libyz.a
$ ar rcs libyz.a qr.o

Retry the linkage that just failed:

$ gcc -o prog main.o libx.a libyz.a -Wl,-trace-symbol=foo,-trace-symbol=bar
/usr/bin/ld: main.o: reference to foo
/usr/bin/ld: main.o: reference to bar
/usr/bin/ld: libx.a(p.o): definition of foo
/usr/bin/ld: libyz.a(qr.o): definition of bar

This time, no error. The definition of foo from libx.a(p.o) is linked. The weak definition from libyz.a(qr.o) is ignored. The program runs like:

$ ./prog
foo from p.c
bar from qr.c

If a symbol definition isn't weak, it is strong. The linker's rules are:

  • At most one strong definition of a symbol can be linked.
  • If one or more weak definitions, as well as a strong definition, are input, then the one strong definition is linked and the all the weak ones are ignored.
  • If only weak definitions are input, then the linker can pick any one of them arbitrarily. (In practice, it picks whichever one if finds first).

Do not use weak symbol definitions simply to escape from multiple definition errors that take you by surprise. Such surprises mean that you don't understand your linkage. Analyse it and fix it so that you are no longer trying to make a program containing multiple definitions of the same thing.

Weak symbol definitions are usually generated by your compiler, behind the scenes, to implement features of the source language that require them ( e.g. global inline function definitions, or template instantiations in C++). Use them yourself only if you understand exactly why you want the linker to input multiple definitions of the same symbol.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
4

What happens is implementation specific (and could be not even specified in C11 standard n1570, but I leave you to check that; AFAIK that standard don't speak of libraries - just of translation units). I believe the scenario you gives is some undefined behavior, so you could be scared (since anything is allowed to happen). And it probably is implementation defined.

In practice, on my Linux system, I'll get some error at link time (at least for static libraries). Read the Program Library HowTo and the documentation of the ld linker. Be aware that the GCC compiler is (sometimes) running ld (with many extra options, which are usually hidden to you). So invoke gcc with the -v flag (to understand how ld is started by your gcc command).

If you link two shared libraries on Linux, it becomes more interesting. But read Drepper's How to Write Shared Libraries. IIRC, the first symbol definition overrides the second. For example, you could use jemalloc and link the -ljemalloc ahead of the implicit -lc (with both libraries defining a malloc symbol). Be also aware of plugins and of the dynamic linker (on Linux, see dlopen(3) & dlsym(3) & ld-linux.so(8)).

Linux also has weak symbols, see this, and its GCC has visibility attribute.

On Windows (which I don't know and never used) things could be different. However, you'll still be able some "multiply defined symbol" error at link time.

The linker works in an operating system specific way. So read Levine's Linkers and Loaders (which explains how both Windows and Unix linkers work, and the details are different). To learn more about OSes, read Operating Systems: Three Easy Pieces (freely downloadable).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • In practice, on your Linux system, you do not get some error at link time just because two libraries (static or otherwise) contain the same symbol. And saying the first “overrides” the second is partly true in a sense but is not a good description of the linking process. Generally (subject to command-line options and so on), the linker selects modules to extract from libraries based on demand: Each module is linked in if it supplies a definition for a symbol that is referenced but not yet defined… – Eric Postpischil Jan 05 '19 at 13:45
  • … So, if two libraries merely define the same symbol, there is no error. (You can even have the same symbol defined multiple times in one library, in different modules.) An error arises only if multiple definitions are actually linked in, which may occur when a module is linked in because it defines a symbol that is needed and it also defines a symbol that was previously defined. – Eric Postpischil Jan 05 '19 at 13:47