c++ – What is an undefined reference/unresolved external symbol error and how do I fix it?

Your linkage consumes libraries before the object files that refer to them

  • You are trying to compile and link your program with the GCC toolchain.
  • Your linkage specifies all of the necessary libraries and library search paths
  • If libfoo depends on libbarthen your linkage correctly puts libfoo before libbar.
  • Your linkage fails with undefined reference to something errors.
  • But all the undefined somethings are declared in the header files you have
    #included and are in fact defined in the libraries that you are linking.

Examples are in C. They could equally well be C++

A minimal example involving a static library you built yourself

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

You build your static library:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

You compile your program:

$ gcc -I. -c -o eg1.o eg1.c

You try to link it with libmy_lib.a and fail:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

The same result if you compile and link in one step, like:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

A minimal example involving a shared system library, the compression library libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%sn",zlibVersion());
    return 0;
}

Compile your program:

$ gcc -c -o eg2.o eg2.c

Try to link your program with libz and fail:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

Same if you compile and link in one go:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

And a variation on example 2 involving pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

What are you doing wrong?

In the sequence of object files and libraries you want to link to make your program, you are placing the libraries before the object files that refer to them. You need to place the libraries after the object files that refer to them.

Link example 1 correctly:

$ gcc -o eg1 eg1.o -L. -lmy_lib

Success:

$ ./eg1 
Hello World

Link example 2 correctly:

$ gcc -o eg2 eg2.o -lz

Success:

$ ./eg2 
1.2.8

Link the example 2 pkg-config variation correctly:

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

The explanation

Reading is optional from here on.

By default, a linkage command generated by GCC, on your distro, consumes the files in the linkage from left to right in the commandline sequence. When it finds that a file refers to something
and does not contain a definition for it, to will search for a definition in files further to the right. If it eventually finds a definition, the reference is resolved. If any references remain unresolved at the end, the linkage fails: the linker does not search backwards.

First, example 1with static library my_lib.a

A static library is an indexed archive of object files. When the linker finds -lmy_lib in the linkage sequence and figures out that this refers to the static library ./libmy_lib.ait wants to know whether your program needs any of the object files in libmy_lib.a.

There is only object file in libmy_lib.anamely my_lib.oand there’s only one thing defined in my_lib.onamely the function hw.

The linker will decide that your program needs my_lib.o if and only if it already knows that your program refers to hwin one or more of the object files it has already added to the program, and that none of the object files it has already added contains a definition for hw.

If that is true, then the linker will extract a copy of my_lib.o from the library and add it to your program. Then, your program contains a definition for hwso its references to hw are resolved.

When you try to link the program like:

$ gcc -o eg1 -L. -lmy_lib eg1.o

the linker has not added eg1.o to the program when it sees
-lmy_lib. Because at that point, it has not seen eg1.o. Your program does not yet make any references to hw: it does not yet make any references at allbecause all the references it makes are in eg1.o.

So the linker does not add my_lib.o to the program and has no further use for libmy_lib.a.

Next, it finds eg1.o, and adds it to be programme. An object file in the linkage sequence is always added to the program. Now, the program makes a reference to hwand does not contain a definition of hw; but there is nothing left in the linkage sequence that could provide the missing definition. The reference to hw ends up unresolvedand the linkage fails.

Second, example 2with shared library libz

A shared library isn’t an archive of object files or anything like it. It’s much more like a program that doesn’t have a main function and instead exposes multiple other symbols that it defines, so that other programs can use them at runtime.

Many Linux distros today configure their GCC toolchain so that its language drivers (gcc,g++,gfortran etc) instruct the system linker (ld) to link shared libraries on an as-needed basis. You have got one of those distros.

This means that when the linker finds -lz in the linkage sequence, and figures out that this refers to the shared library (say) /usr/lib/x86_64-linux-gnu/libz.soit wants to know whether any references that it has added to your program that aren’t yet defined have definitions that are exported by libz

If that is true, then the linker will not copy any chunks out of libz and add them to your program; Instead, it will just doctor the code of your program so that:-

  • At runtime, the system program loader will load a copy of libz into the same process as your program whenever it loads a copy of your program, to run it.

  • At runtime, whenever your program refers to something that is defined in
    libzthat reference uses the definition exported by the copy of libz in the same process.

Your program wants to refer to just one thing that has a definition exported by libznamely the function zlibVersionwhich is referred to just once, in eg2.c. If the linker adds that reference to your program, and then finds the definition exported by libzthe reference is resolved

But when you try to link the program like:

gcc -o eg2 -lz eg2.o

the order of events is wrong in just the same way as with example 1. At the point when the linker finds -lzthere are no references to anything in the program: they are all in eg2.o, which has not yet been seen. So the linker decides it has no use for libz. When it reaches eg2.oadds it to the program, and then has undefined reference to zlibVersion, the linkage sequence is finished; that reference is unresolved, and the linkage fails.

Lastly, the pkg-config variation of example 2 has a now obvious explanation. After shell-expansion:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

becomes:

gcc -o eg2 -lz eg2.o

which is just example 2 again.

I can reproduce the problem in example 1, but not in example 2

The linkage:

gcc -o eg2 -lz eg2.o

works just fine for you!

(Or: That linkage worked fine for you on, say, Fedora 23, but fails on Ubuntu 16.04)

That’s because the distro on which the linkage works is one of the ones that does not configure its GCC toolchain to link shared libraries as-needed.

Back in the day, it was normal for unix-like systems to link static and shared libraries by different rules. Static libraries in a linkage sequence were linked on the as-needed basis explained in example 1, but shared libraries were linked unconditionally.

This behavior is economical at linktime because the linker doesn’t have to ponder whether a shared library is needed by the program: if it’s a shared library, link it. And most libraries in most linkages are shared libraries. But there are disadvantages too:-

  • It is uneconomical at runtimebecause it can cause shared libraries to be loaded along with a program even if it doesn’t need them.

  • The different linkage rules for static and shared libraries can be confusing to inexpert programmers, who may not know whether -lfoo in their linkage is going to resolve to /some/where/libfoo.a or to /some/where/libfoo.soand might not understand the difference between shared and static libraries anyway.

This trade-off has led to the schismatic situation today. Some distros have changed their GCC linkage rules for shared libraries so that the as-needed
principle applies for all libraries. Some distros have stuck with the old way.

Why do I still get this problem even if I compile-and-link at the same time?

If I just do:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

sure gcc has to compile eg1.c first, and then link the resulting object file with libmy_lib.a. So how can it not know that object file is needed when it’s doing the linking?

Because compiling and linking with a single command does not change the order of the linkage sequence.

When you run the command above, gcc figures out that you want compilation + linkage. So behind the scenes, it generates a compilation command, and runs it, then generates a linkage command, and runs it, as if you had run the two commands:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

So the linkage fails just as it does if you do run those two commands. The only difference you notice in the failure is that gcc has generated a temporary object file in the compile + link case, because you’re not telling it to use eg1.o. We see:

/tmp/ccQk1tvs.o: In function `main'

instead of:

eg1.o: In function `main':

See also

The order in which interdependent linked libraries are specified is wrong

Putting interdependent libraries in the wrong order is just one way in which you can get files that need definitions of things coming later in the linkage than the files that provide the definitions. Putting libraries before the object files that refer to them is another way of making the same mistake.

Leave a Comment