constructor / destructor
3 Code Demos
Intermediate
TLPI Ch 42.4
A shared library often needs to set up internal state when it is first loaded โ for example, opening a database connection, allocating memory, starting a background thread, or initializing a mutex. Similarly, when the library is unloaded, it needs to clean up โ close files, free memory, destroy threads.
The constructor and destructor GCC attributes allow you to declare functions that run automatically at library load and unload time. You don’t need to call them explicitly โ the dynamic linker takes care of it.
| Program starts dlopen() or auto-load |
โ | constructor runs __attribute__((constructor)) |
โ | Library in use Normal function calls |
โ | destructor runs __attribute__((destructor)) |
GCC provides two function attributes: __attribute__((constructor)) and __attribute__((destructor)). These are the recommended, modern approach.
/* mylib.c โ shared library with init and cleanup */
#include <stdio.h>
#include <stdlib.h>
/* This runs automatically when the library is loaded */
void __attribute__((constructor)) mylib_init(void)
{
printf("[mylib] Library loaded โ running initialization\n");
/* Typical tasks: open log file, initialize mutex, allocate globals */
}
/* This runs automatically when the library is unloaded */
void __attribute__((destructor)) mylib_fini(void)
{
printf("[mylib] Library unloaded โ running cleanup\n");
/* Typical tasks: close log file, destroy mutex, free globals */
}
/* A normal exported function */
void mylib_greet(void)
{
printf("[mylib] Hello from mylib_greet()!\n");
}
# Build the shared library
gcc -g -c -fPIC -Wall mylib.c
gcc -g -shared -o libmylib.so mylib.o
/* main.c โ program using the library */
#include <stdio.h>
void mylib_greet(void);
int main(void)
{
printf("[main] Before calling mylib_greet()\n");
mylib_greet();
printf("[main] After calling mylib_greet()\n");
return 0;
}
# Build and run
gcc -g -o prog main.c -L. -lmylib
LD_LIBRARY_PATH=. ./prog
# Expected output:
# [mylib] Library loaded โ running initialization
# [main] Before calling mylib_greet()
# [mylib] Hello from mylib_greet()!
# [main] After calling mylib_greet()
# [mylib] Library unloaded โ running cleanup
main() is called. The destructor runs after main() returns (or after exit() is called). This is true whether the library is loaded automatically at startup or explicitly via dlopen().One advantage of the modern approach over the old _init() style is that you can define multiple constructor and destructor functions, and control their execution order using priority numbers.
Lower priority numbers run first for constructors. For destructors, the order is reversed (higher priority runs first).
/* multi_init.c โ library with multiple init/fini functions */
#include <stdio.h>
/* Priority 101 โ runs first among our custom constructors */
void __attribute__((constructor(101))) init_logger(void)
{
printf("[INIT 101] Logger initialized\n");
}
/* Priority 102 โ runs second */
void __attribute__((constructor(102))) init_database(void)
{
printf("[INIT 102] Database connection opened\n");
}
/* Priority 103 โ runs third */
void __attribute__((constructor(103))) init_cache(void)
{
printf("[INIT 103] Cache warmed up\n");
}
/* Destructors run in reverse order: 103 first, then 102, then 101 */
void __attribute__((destructor(101))) cleanup_logger(void)
{
printf("[FINI 101] Logger closed\n");
}
void __attribute__((destructor(102))) cleanup_database(void)
{
printf("[FINI 102] Database connection closed\n");
}
void __attribute__((destructor(103))) cleanup_cache(void)
{
printf("[FINI 103] Cache flushed\n");
}
void do_work(void)
{
printf("[WORK] Doing library work...\n");
}
# Build and run
gcc -g -c -fPIC -Wall multi_init.c
gcc -g -shared -o libmulti.so multi_init.o
gcc -g -o testprog testmain.c -L. -lmulti
LD_LIBRARY_PATH=. ./testprog
# Output:
# [INIT 101] Logger initialized
# [INIT 102] Database connection opened
# [INIT 103] Cache warmed up
# [WORK] Doing library work...
# [FINI 103] Cache flushed
# [FINI 102] Database connection closed
# [FINI 101] Logger closed
Before GCC attributes, shared libraries used special functions named _init() and _fini(). These are now obsolete but you may encounter them in older codebases.
/* old_lib.c โ old-style init/fini (OBSOLETE, do not use in new code) */
#include <stdio.h>
/*
* _init() is called when the library is loaded.
* Requires -nostartfiles when linking to avoid linker conflict.
*/
void _init(void)
{
printf("[_init] Old-style library init\n");
}
/*
* _fini() is called when the library is unloaded.
*/
void _fini(void)
{
printf("[_fini] Old-style library cleanup\n");
}
void old_api(void)
{
printf("[old_api] Function called\n");
}
# MUST use -nostartfiles to avoid conflict with linker's default _init/_fini
gcc -g -c -fPIC -Wall old_lib.c
gcc -g -shared -nostartfiles -o libold.so old_lib.o
_init() and _fini() are obsolete. They have one major limitation: you can only have one of each per library. The modern __attribute__((constructor)) approach lets you have multiple init functions with priority ordering, and works without needing -nostartfiles.| Feature | __attribute__((constructor)) | _init() / _fini() |
|---|---|---|
| Status | โ Modern, recommended | โ Obsolete |
| Multiple functions | Yes โ many constructors per library | No โ only one _init / one _fini |
| Priority control | Yes โ constructor(N) | No |
| Works in main prog | Yes | No |
| Special link flag | None needed | Needs -nostartfiles |
A very common real-world use case is initializing a mutex (or pthread resources) when a thread-safe library loads:
/* thread_safe_lib.c โ library that auto-initializes a mutex */
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
static pthread_mutex_t lib_mutex;
static int call_count = 0;
void __attribute__((constructor)) lib_init(void)
{
if (pthread_mutex_init(&lib_mutex, NULL) != 0) {
fprintf(stderr, "[lib] FATAL: mutex init failed\n");
exit(EXIT_FAILURE);
}
printf("[lib] Mutex initialized\n");
}
void __attribute__((destructor)) lib_fini(void)
{
pthread_mutex_destroy(&lib_mutex);
printf("[lib] Mutex destroyed. Total calls: %d\n", call_count);
}
/* Thread-safe exported function */
void lib_do_something(void)
{
pthread_mutex_lock(&lib_mutex);
call_count++;
printf("[lib] Call #%d (thread-safe)\n", call_count);
pthread_mutex_unlock(&lib_mutex);
}
# Build
gcc -g -c -fPIC -Wall thread_safe_lib.c
gcc -g -shared -o libtsafe.so thread_safe_lib.o -lpthread
__attribute__((constructor)) attribute: void __attribute__((constructor)) my_init(void) { ... }. This function is called automatically by the dynamic linker when the library is loaded, before main() is entered.-nostartfiles link flag. The old _init()/_fini() approach is obsolete and limited to one function each.main() is called. The destructor runs after main() returns or after exit() is called. This is true whether the library is loaded at program startup or explicitly via dlopen()._init() and _fini(). If you define your own versions without -nostartfiles, you get a linker error about duplicate symbol definitions.__attribute__((constructor(101))). Lower numbers run first for constructors; destructors run in reverse order. Priorities 0โ100 are reserved by the system โ use 101 or higher.main().