Building Static and Shared Libraries with GCC on Linux
Static libraries bundle multiple object files into a single archive, while shared libraries defer symbol resolution until load or runtime. Both forms are comon on Linux and can be produced with GCC and standard binutils.
Inspecting a system static library
Static archives are regullar files with the .a suffix, typically created with ar. For example, glibc’s static C runtime archive can be examined to see which objects it includes.
List members and look for fread-related objects:
$ ar -t "$(gcc -print-file-name=libc.a)" | grep fread
Locate objects related to printf using objdump:
$ objdump -t "$(gcc -print-file-name=libc.a)" | grep 'printf\.o'
These commansd reveal object members such as iofread.o, vprintf.o, printf.o, etc., all packaged into libc.a.
Dynamic vs. static linking with a tiny program
hello.c:
#include <stdio.h>
int main(void) {
printf("Hello, C!\n");
return 0;
}
Default build (typically dynamically linked):
$ gcc hello.c -o hello
$ ls -lh hello
Static build (links in libc and friends, producing a much larger binary):
$ gcc -static hello.c -o hello
$ ls -lh hello
The statically linked executable will be significantly larger because library code is embedded into the binary.
Creating static and shared libraries with GCC
Source files
ops.h:
#ifndef OPS_H
#define OPS_H
void set_base(int value);
int add_to(int value);
#endif
ops.c:
#include <stdio.h>
#include "ops.h"
static int g_base;
void set_base(int value) {
g_base = value;
}
int add_to(int value) {
return g_base + value;
}
__attribute__((constructor)) static void lib_init(void) {
printf("libcalcutil: init\n");
g_base = 0;
}
__attribute__((destructor)) static void lib_fini(void) {
printf("libcalcutil: fini\n");
}
solver.h:
#ifndef SOLVER_H
#define SOLVER_H
int ultimate_answer(void);
#endif
solver.c:
#include "ops.h"
#include "solver.h"
int ultimate_answer(void) {
set_base(20);
return add_to(22); // 42
}
main.c:
#include <stdio.h>
#include "ops.h"
#include "solver.h"
int main(void) {
set_base(5);
printf("5 + 7 = %d\n", add_to(7));
printf("And the answer is: %d\n", ultimate_answer());
return 0;
}
Directory layout
$ mkdir -p build/static build/shared
$ tree -L 2
.
├── build
│ ├── shared
│ └── static
├── main.c
├── ops.c
├── ops.h
├── solver.c
└── solver.h
Compile object files
Common object for the main program:
$ gcc -c main.c -o build/main.o
Static library objects:
$ gcc -c ops.c -o build/static/ops.o
$ gcc -c solver.c -o build/static/solver.o
Shared library objects (position-independent):
$ gcc -fPIC -c ops.c -o build/shared/ops.o
$ gcc -fPIC -c solver.c -o build/shared/solver.o
Build a static library and link
Create the archive:
$ ar rcs build/static/libcalcutil.a build/static/ops.o build/static/solver.o
Link statically againsst the archive:
$ gcc build/main.o -Lbuild/static -lcalcutil -o build/app-static
Run:
$ ./build/app-static
libcalcutil: init
5 + 7 = 12
And the answer is: 42
libcalcutil: fini
Build a shared library and link
Create the shared object:
$ gcc -shared build/shared/ops.o build/shared/solver.o -o build/shared/libcalcutil.so
Link an executable that uses the shared object:
$ gcc build/main.o -Lbuild/shared -lcalcutil -o build/app-shared
Run (initial attempt may fail if the loader cannot locate the .so):
$ ./build/app-shared
./build/app-shared: error while loading shared libraries: libcalcutil.so: cannot open shared object file: No such file or directory
Point the dynamic loader to the directory containing the library for this shell session:
$ export LD_LIBRARY_PATH="$(pwd)/build/shared:${LD_LIBRARY_PATH}"
$ ./build/app-shared
libcalcutil: init
5 + 7 = 12
And the answer is: 42
libcalcutil: fini
Alternaitvely, place the shared library in a standard search path (system-wide change may require root):
$ sudo mv build/shared/libcalcutil.so /usr/lib/
$ sudo chmod 755 /usr/lib/libcalcutil.so
# Optionally: sudo ldconfig