The job of the linker is to link together a bunch of object files (.o files) into a binary executable. The process of linking mainly involves resolving symbolic addresses to numerical addresses. The result of the link process is normally an executable program.

During the link process, the linker will pick up all the object modules specified on the command line, add some system-specific startup code in front and try to resolve all external references in the object module with external definitions in other object files (object files can be specified directly on the command line or may implicitly be added through libraries). It will then assign load addresses for the object files, that is, it specifies where the code and data will end up in the address space of the finished program. Once it’s got the load addresses, it can replace all the symbolic addresses in the object code with “real”, numerical addresses in the target’s address space. The program is ready to be executed now.

This includes both the object files that the compiler created from your source code files as well as object files that have been pre-compiled for you and collected into library files. These files have names which end in .a or .so, and you normally don’t need to know about them, as the linker knows where most of them are located and will link them in automatically as needed.

Implicit invocation of the linker

Like the pre-processor, the linker is a separate program, often called ld (but Linux uses collect2, for example). Also like the pre-processor, the linker is invoked automatically for you when you use the compiler. Thus, the normal way of using the linker is as follows:

% gcc foo.o bar.o baz.o -o myprog

This line tells the compiler to link together three object files (foo.o, bar.o, and baz.o) into a binary executable file named myprog. Now you have a file called myprog that you can run and which will hopefully do something cool and/or useful.

Explicit invocation of the linker

It is possible to invoke the linker directly, but this is seldom advisable, and is typically very platform-specific. That is, options that work on Linux won’t necessarily work on Solaris, AIX, macOS, Windows, and similarly for any other platform. If you work with GCC, you can use gcc -v to see what is executed on your behalf.

Options for the linker

The linker also takes some arguments to modify it’s behavior. The following command would tell gcc to link foo.o and bar.o, but also include the ncurses library.

% gcc foo.o bar.o -o foo -lncurses

This is actually (more or less) equivalent to

% gcc foo.o bar.o /usr/lib/libncurses.so -o foo

(although libncurses.so could be libncurses.a, which is just an archive created with ar). Note that you should list the libraries (either by pathname or via -lname options) after the object files. With static libraries, the order that they are specified matters; often, with shared libraries, the order doesn’t matter.

Note that on many systems, if you are using mathematical functions (from <math.h>), you need to specify -lm to load the mathematics library — but Mac OS X and macOS Sierra do not require this. There are other libraries that are separate libraries on Linux and other Unix systems, but not on macOS — POSIX threads, and POSIX realtime, and networking libraries are examples. Consequently, the linking process varies between platforms.

Other compilation options

This is all you need to know to begin compiling your own C programs. Generally, we also recommend that you use the -Wall command-line option:

% gcc -Wall -c foo.cc

The -Wall option causes the compiler to warn you about legal but dubious code constructs, and will help you catch a lot of bugs very early.