Creating Shared C Library with GCC
Introduction
For ease of usage and re-usability we can compile C procedures using GCC and distribute as library. By compiling as shared or dynamic library we can share a single source of library across different applications in system. This reduces the overall size of application but need to mange dependency and path of shared libraries.
Compiling Application Without Library
For this article we will use a simple program. First we will run the program, then will split into files and reuse some part as shared library.
// main.c
#include <stdio.h>
void printDecorated(char* message) {
printf("- %s -\n", message);
}
void printGreetings(int n) {
for (int i = 0; i < n; i++) {
printDecorated("Hello");
}
}
int main () {
printGreetings(3);
return 0;
}
Now we can compile and run using following commands, -o for naming the executable:
$ gcc -o main main.c
$ ./main
- Hello -
- Hello -
- Hello -
Now we can reorganize the functions in separate files. Lets move printDecorate function in decorate.c and printGreetings in greeter.c file. We also create a header file with these function prototypes greet.h and update include statements.
// greet.h
void printGreetings(int);
void printDecorated(char *);
// decorate.c
#include <stdio.h>
void printDecorated(char* message) {
printf("- %s -\n", message);
}
// greeter.c
#include <stdio.h>
#include "greet.h"
void printGreetings(int n) {
for (int i = 0; i < n; i++) {
printDecorated("Hello");
}
}
// main.c
#include <stdio.h>
#include "greet.h"
int main () {
printGreetings(3);
return 0;
}
For compiling and running we use following commnads:
$ gcc -o main decorate.c greeter.c main.c
$ ./main
- Hello -
- Hello -
- Hello -
Note we have included using "greet.h"
not <greet.h>
. This will work as long as greet.h is in same folder. By
default gcc
searches for include files in /usr/include/
or /usr/local/include/
. We can also add additional path
using gcc
-I option.
Now if we change the include statement of main.c and greeter.c to <greet.h>
. We can compile like below:
$ gcc -I "$PWD" -o main decorate.c greeter.c main.c
Or we can copy greet.h in /usr/include/
or /usr/local/include/
, then we can compiler directly.
$ gcc -o main decorate.c greeter.c main.c
Compiling and Running Shared Library
As the compiled code will be shared across multiple applications we have to compile differently with pic (Position Independent Code) option. This will make sure that library doesn’t store data at fixed addresses which will cause issue when shared across multiple applications.
$ ls
decorate.c greeter.c greet.h main.c
# Added -I for including greet.h in $PWD
$ gcc -I "$PWD" -fpic -c greeter.c decorate.c
$ ls
decorate.c decorate.o greeter.c greeter.o greet.h main.c
Now create a shared library using following command. Library must be prefixed with lib and shared library are suffixed with .so. Here libgreet.so is the name of library.
$ gcc -shared -o libgreet.so decorate.o greeter.o
Now lets compile the main.c using newly created shared library.
$ ls
decorate.c decorate.o greeter.c greeter.o greet.h libgreet.so main.c
# -L include addtional library, by default searches in /usr/local/lib/ or /usr/lib/
# -l uses libgreet.so library, note only `greet` is given
$ gcc -I "$PWD" -L "$PWD" -o main main.c -lgreet
Now if we copy the libgreet.a in /usr/local/lib/
or /usr/lib/
and greet.h in /us/local/include/
or
/usr/include/
, then we can compile like below:
$ gcc -o main main.c -lgreet
Now lets try to run the application.
$ ./main
./main: error while loading shared libraries: libgreet.so: cannot open shared object file: No such file or directory
# We can check runtime dependencies using `ldd`
$ ldd ./main
linux-vdso.so.1 (0x00007ffc71da5000)
libgreet.so => /home/john/greet/libgreet.so (0x00007f633013b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f632fd4a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f633053f000)
We can see using ldd
that compile reference of libgreet.so is added but ./main doesn’t know the runtime path
of libgreet.so. We can add the runtime path using $LD_LIBRARY_PATH environment variable.
$ export LD_LIBRARY_PATH="$PWD:$LD_LIBRARY_PATH"
$ ./main
- Hello -
- Hello -
- Hello -
ldconig
keeps track of all shared library configured in /etc/ld.so.conf.d
. We can list currently knows shared
library using following command:
$ ldconfig -p
ldcondig
searched both /usr/local/lib/
and /usr/lib/
for dependencies. After we copy libgreet.so to lib
folder we can run following command to update the shared library list.
$ sudo ldconfig
Now we can run main directly without exporting library path in $LD_LIBRARY_PATH.
Creating Makefile
Now lets write these steps in a make file. Create a file Makefile in same directory and copy following code.
.DEFAULT_GOAL:= run
.SILENT:
libgreet.so: greeter.c decorate.c greet.h
gcc -I. -fpic -c greeter.c decorate.c
gcc -shared -o libgreet.so decorate.o greeter.o
build: greet.h main.c libgreet.so
gcc -I. -L. main.c -o main -lgreet
clean:
rm -f greeter.o decorate.o libgreet.so main
run: build
LD_LIBRARY_PATH=".:$LD_LIBRARY_PATH" ./main && make -s clean
Now run using following command:
$ make
Tools Version
- gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
- GNU Make 4.1