Quick topics on compiling and linking in C/C++

From Wiki**3

This page provides basic examples of C/C++ compilation and linking. The objective is to provide a quick (although not complete) tour of how to organize code.

Even though neither C nor C++ impose code organization in files (this is done by, for instance, Java, where each public class must be defined in a file with the exact name of that class), we use a similar division as a way of producing better code. Thus, interfaces will reside in .h (header) files, and implementations will reside in .c or .cpp files. These latter sets of files are what we will call compilation units. File names will either be classes or will reflect that code functionality (this will be the norm, mostly, for C).

First Scenario: Without Libraries

The first scenario considers the following files, used to build the soup application:

  • soup.c - main body; uses the other components (uses .h files)
  • potato.c - potato implementation (interface in potato.h)
  • carrot.c - potato implementation (interface in carrot.h)
  • tomato.c - potato implementation (interface in tomato.h)

We assume the C language, but it could also be C++ (the idea is the same).

We assume that each vegetable is implemented as a function called by the main function. Further, we assume that all the vegetables are independent from each other (and that the soup depends on each of them).

Compiling each part

All parts are compiled separately, using the same command, but specific files.

 gcc -c soup.c -o soup.o
 gcc -c potato.c -o potato.o
 gcc -c carrot.c -o carrot.o
 gcc -c tomato.c -o tomato.o

Note that the -o part is optional in these commands.

Linking and running

Linking is carried out by the linker. In this case, we invoke it through GCC, so that we also get the C library and runtime and not just our .o files.

 gcc -o soup soup.o potato.o carrot.o tomato.o

Note that each vegetable provided a function needed by the soup and that the soup had unresolved references to such functions. It is the linker's task to "connect" (resolve/link) the references.

Second Scenario: Using Static Libraries

The basic assumptions are the same as in the first scenario. We assume that each vegetable is implemented as a function called by the main function. Further, we assume that all the vegetables are independent from each other (and that the soup depends on each of them).

As before, we assume the C language, but it could also be C++ (the idea is the same).

Compiling each part

All parts are compiled separately, using the same command, but specific files.

 gcc -c soup.c -o soup.o
 gcc -c potato.c -o potato.o
 gcc -c carrot.c -o carrot.o
 gcc -c tomato.c -o tomato.o

Note that the -o part is optional in these commands.

Creating the library

A static library is an archive of .o files. It can be created by the following command.

 ar cr libvegetables.a potato.o carrot.o tomato.o

More information on ar can be found in the manual pages. A Wikipedia page also exists: Ar_(Unix).

Linking and running

Linking is carried out by the linker. In this case, we invoke it through GCC, so that we also get the C library and runtime and not just our .o files.

If the library in installed in a standard location, the following command is sufficient to produce the executable. The -l flag assumes that the library is named libNAME.a (in this case, the flag is simply -lNAME).

 gcc -o soup soup.o -lvegetables

If the library in not installed, then its location may be specified in the linking command.

 gcc -o soup soup.o -Llocation/of/the/library -lvegetables

Or alternatively (less preferable):

 gcc -o soup soup.o location/of/the/library/libvegetables.a

Note that each vegetable provided a function needed by the soup and that the soup had unresolved references to such functions. It is the linker's task to "connect" (resolve/link) the references (as in the first scenario).

Third Scenario: Using Dynamic Libraries

Unlike static libraries, which are baked directly into the final executable, dynamic (or shared) libraries are loaded at runtime. This keeps the executable smaller and allows multiple programs to share the same library in memory.

Creating the library

To create a dynamic library, the source files must first be compiled into Position Independent Code (PIC). This is done using the -fPIC flag.

 gcc -c -fPIC potato.c carrot.c tomato.c

Once the object files are created, we use GCC with the -shared flag to build the dynamic library, typically given a .so (shared object) extension.

 gcc -shared -o libvegetables.so potato.o carrot.o tomato.o

Linking and running

Linking against a dynamic library looks identical to linking against a static one:

 gcc -o soup soup.o -Llocation/of/the/library -lvegetables

However, running the executable is different. Because the linker only verified that the symbols existed at compile time, the operating system's dynamic linker must be able to find libvegetables.so at runtime. If the library is not installed in a standard system location (like /usr/lib), you must tell the system where to find it by setting the LD_LIBRARY_PATH environment variable.

 export LD_LIBRARY_PATH=location/of/the/library:$LD_LIBRARY_PATH
 ./soup

Fourth Scenario: Using Dynamic Modules

Much like dynamic libraries, but each module is made into a shared object. This is often used when an application is composed of distinct, swappable components.

Creating shared objects

Instead of bundling all vegetables into a single libvegetables.so, we compile each into its own independent shared object. Again, we require the -fPIC and -shared flags.

 gcc -c -fPIC potato.c carrot.c tomato.c
 gcc -shared -o libpotato.so potato.o
 gcc -shared -o libcarrot.so carrot.o
 gcc -shared -o libtomato.so tomato.o

Linking and running

To compile the main soup application, we must link against all the individual modules we intend to use:

 gcc -o soup soup.o -Llocation/of/the/modules -lpotato -lcarrot -ltomato

As in the third scenario, the modules must be locatable at runtime.

<syntaxhighlight lang="shell">
 export LD_LIBRARY_PATH=location/of/the/modules:$LD_LIBRARY_PATH
 ./soup

== Fifth Scenario: Using Run-time Dynamic Modules ==

There is no explicit dependency between modules: symbols are used as strings. In this scenario, the main application (soup) is not linked against the vegetables at compile time at all. It does not even need to know they exist until runtime.

This is typically how "plugin" systems are implemented in C/C++. The application uses the POSIX '''<dlfcn.h>''' API to load shared objects on the fly.

=== Sample application ===

For this scenario, the vegetables are compiled exactly as they were in the Fourth Scenario (creating '''libpotato.so''', etc.). However, the main '''soup.c''' code looks completely different. It no longer includes '''potato.h''' or calls the functions directly.

<syntaxhighlight lang="c">
 int main() {
 void *potato_handle;
 void (*prepare_potato)(void);
 char *error;
 
 ```
 /* 1. Open the dynamic module at runtime */
 potato_handle = dlopen("./libpotato.so", RTLD_LAZY);
 if (!potato_handle) {
     fprintf(stderr, "Failed to load potato plugin: %s\n", dlerror());
     exit(1);
 }
 
 /* Clear any existing errors */
 dlerror();
 
 /* 2. Locate the symbol (function) by its string name */
 *(void **) (&prepare_potato) = dlsym(potato_handle, "potato_function");
 
 if ((error = dlerror()) != NULL)  {
     fprintf(stderr, "Failed to locate symbol: %s\n", error);
     exit(1);
 }
 
 /* 3. Execute the dynamically loaded function */
 printf("Making the soup...\n");
 prepare_potato();
 
 /* 4. Close the module when finished */
 dlclose(potato_handle);
 
 return 0;
 
 ```
 
 }

To compile this application, you must link against the dynamic linking library (libdl) using the -ldl flag. You do not link against the vegetable modules.

 gcc -o soup soup.c -ldl

Because the library paths are hardcoded in the source code (e.g., ./libpotato.so) or resolved via configuration files at runtime, there is no need to modify LD_LIBRARY_PATH as long as the application knows where to look for its plugins.