Dynamic and static libraries in C

Shelves that look like boxes hold vintage books upon a wooden wall.
Photo by Paul Melki on Unsplash

When working on large projects, it can be frustrating to compile many files together. This is even more of an issue when sharing the files with others. Libraries enable us to organize and use functions without having to copy the source code into each location.

If you’ve done any programming in C, you’ve likely begun your file with something like

include <stdlib.h>

When you do this, you are calling the header function for the standard library. Each of the files in the standard library exist as source code, but you don’t have to compile them into your file because the compiler already has access to the library. You can create your own library to function similarly.

There are two different types of libraries, static and dynamic. A static library, also known as an archive, is an indexed library that the compiler references only once. The linker copies the code of the library when making an executable. Static libraries take up more memory, but they can speed up run time.

A dynamic library, also known as a shared library, links the code while running the executable, not during compilation. Instead of copying the code, the compiler simply checks that all object files are present during the linking stage. The dynamic loader then loads the shared files into memory before running the executable. Dynamic libraries are beneficial because they do not require a user to recompile the main file every time there is an update in the library. However, they can hurt the speed of an executable.

Creating libraries

In order to create a library, first create a header file containing all prototypes that you intend to include. Then, you’ll need to compile your code.

$ gcc -c *.c

The -c flag instructs the compiler to take the files through every compilation stage except for the linker. If you are creating a dynamic library, you will also need to use the -fPIC flag. This creates position-independent code, meaning that it will execute correctly no matter where it is in memory.

To create a static library, use the archiver command:

$ ar rc libfoo.a *.o

The r flag instructs the archiver to replace old versions of files with new ones while the c flag instructs it to create a new archive if one does not already exist. All static libraries end in .a for archive.

Static libraries must be indexed after creation. This organizes the library’s symbols, making it easier and faster to access them.

$ ranlib newlibrary.a
$ gcc -shared -o libfoo.so *.o

The -shared flag creates the shared library. All dynamic libraries end in .so for shared object.

Let’s use the ldd command to print our shared object dependencies.

myvm:~/library_practice $ gcc -L. main.c libholberton.so -o check
myvm:~/library_practice$ ldd check
linux-vdso.so.1 => (0x00007fff27372000)
libfoo.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 => (0x00007fc349e7c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc34a245000)

Finally, export the library path so that the child processes can access it using the environmental variable.

Learn more about child processes in this article, where I break down the shell.

$ export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH

When we check this again using ldd, we can see that the shared dependencies have been updated.

myvm:~/library_practice $ gcc -L. main.c libholberton.so -o check
myvm:~/library_practice$ ldd check
linux-vdso.so.1 => (0x00007fff27372000)
libfoo.so => /home/vagrant/library_practice/libfoo.so (0x00007fd7b2e79000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 => (0x00007fc349e7c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc34a245000)

Once it is created, you can use the nm command to list symbols in your library.

myvm:~/library_practice $ nm liball.so
0000000000000f89 T _abs
0000000000000af7 T _atoi
0000000000202040 B __bss_start
0000000000202040 b completed.6982
w __cxa_finalize@@GLIBC_2.2.5
0000000000000900 t deregister_tm_clones
0000000000000970 t __do_global_dtors_aux
0000000000201e08 t __do_global_dtors_aux_fini_array_entry
0000000000202038 d __dso_handle
0000000000201e18 d _DYNAMIC
0000000000202040 D _edata
0000000000202048 B _end
000000000000103c T _fini
00000000000009b0 t frame_dummy
0000000000201e00 t __frame_dummy_init_array_entry
0000000000001398 r __FRAME_END__
0000000000202000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000000890 T _init
0000000000000de6 T _isalpha
0000000000000b01 T _isdigit
0000000000000c81 T _islower
00000000000009e5 T _isupper
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000201e10 d __JCR_END__
0000000000201e10 d __JCR_LIST__
w _Jv_RegisterClasses
0000000000000b22 T _memcpy
0000000000000a06 T _memset
0000000000001016 T _putchar
0000000000000ca7 T _puts
0000000000000930 t register_tm_clones
0000000000000a41 T _strcat
0000000000000b65 T _strchr
0000000000000ce2 T _strcmp
0000000000000faf T _strcpy
0000000000000baf T _strlen
0000000000000bd9 T _strncpy
0000000000000e1a T _strpbrk
0000000000000d57 T _strspn
0000000000000ee6 T _strstr
0000000000202040 d __TMC_END__
U write@@GLIBC_2.2.5

Using libraries on a Linux system

In the words of Arthur, “Having fun isn’t hard when you’ve got a library card!”

Now that you’ve created your library, it’s time to use it. No matter which type of library you’re using, there are a few things to keep in mind. First, since the compiler will not use the library until the linker stage, all commands related to the library must come after the file. Second, the -l flag will give the compiler the name of the library to reference.

Static libraries are compiled along with the files needed for a program.

$ gcc main.c -L. -lfoo.a -o executablename

When working with static libraries, the -L. flag tells the compiler where to look for the library.

In dynamic libraries, there are no additional flags needed. You can use the library by compiling it with your files.

$ gcc main.c -lfoo -o executablename

Because we already updated the path for the library, we do not need to specify anything using -L.

But don’t forget, dynamic libraries are also called shared libraries. So what happens if we want other people to be able to use them? First, place the library in a common area and update the permissions. Next, use the ldconfig command to update the cache and create a link to the shared library.

Once you have the hang of these steps, you’re ready to create your own libraries. Decorate a bookmark with the time you save on your next project!

Software engineering student and lover of mountains.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store