Automatic Setup and Teardown in Shared Libraries
Sometimes a shared library needs to perform initialization when it is first loaded and cleanup when it is unloaded. Examples include:
- Allocating a global resource pool (thread pool, connection pool, buffer cache)
- Registering the library with a central manager or logging system
- Opening a log file or database connection
- Setting up signal handlers or timers
You could require the caller to always call lib_init() and lib_cleanup() explicitly, but this is error-prone — callers forget. Linux provides a better way: constructor and destructor functions that run automatically when the library loads and unloads.
The Constructor and Destructor Attributes
GCC provides two special function attributes for initialization and finalization:
/* Syntax: mark a function to run automatically when the library LOADS */
void __attribute__ ((constructor)) my_lib_init(void)
{
/* Initialization code runs here BEFORE any user code calls the library */
}
/* Syntax: mark a function to run automatically when the library UNLOADS */
void __attribute__ ((destructor)) my_lib_fini(void)
{
/* Cleanup code runs here AFTER all user code is done with the library */
}
Key rules:
- Both must return
voidand take no arguments. - The function names can be anything — the attribute is what matters.
- You can define multiple constructor/destructor functions in the same library.
- They work for both startup-loaded libraries and dlopen()-loaded libraries.
- They also work in the main program (not just shared libraries).
(dlopen or program start)
constructor() runs
Normal program execution,
user calls library functions
(dlclose or process exit)
destructor() runs
Multiple Constructor/Destructor Functions and Priority
A library can have multiple constructor functions. They run in an unspecified order unless you specify a priority:
/* Multiple constructors — order unspecified without priority */
void __attribute__((constructor)) init_logging(void) {
printf("init_logging() called\n");
}
void __attribute__((constructor)) init_networking(void) {
printf("init_networking() called\n");
}
/* With priority: lower number runs FIRST (before higher numbers) */
/* Priority range: 101 and above (0–100 are reserved for system use) */
void __attribute__((constructor(101))) init_first(void) {
printf("init_first() — priority 101, runs earliest\n");
}
void __attribute__((constructor(200))) init_second(void) {
printf("init_second() — priority 200, runs after 101\n");
}
/* Destructors with priority: higher number runs FIRST (reverse of constructors) */
void __attribute__((destructor(200))) cleanup_second(void) {
printf("cleanup_second() — priority 200, runs first at unload\n");
}
void __attribute__((destructor(101))) cleanup_first(void) {
printf("cleanup_first() — priority 101, runs last at unload\n");
}
| Priority | Constructor runs | Destructor runs |
|---|---|---|
| 101 (lower) | First (earliest at load) | Last (latest at unload) |
| 200 (higher) | Second | First (earliest at unload) |
The Old Approach: _init() and _fini() (Obsolete)
Before GCC constructor/destructor attributes existed, libraries used two special functions named exactly _init() and _fini():
/* OLD STYLE — DO NOT USE in new code */
#include <stdio.h>
/* Called when library is first loaded */
void _init(void) {
printf("Library loaded via _init()\n");
}
/* Called when library is unloaded */
void _fini(void) {
printf("Library unloaded via _fini()\n");
}
# Must use -nostartfiles to prevent the linker from including
# its own default _init/_fini stubs:
gcc -fPIC -shared -nostartfiles -o mylib.so mylib.c
| Feature | _init()/_fini() (old) | constructor/destructor (modern) |
|---|---|---|
| Multiple functions allowed | No — only one each | Yes — unlimited |
| Works in main program | No | Yes |
| Priority ordering | No | Yes |
| Special linker flag needed | -nostartfiles required | None needed |
| Status | Obsolete | Current best practice |
Code Example 1: Library with Automatic Init and Cleanup
/* resourcelib.c — library that manages a resource pool automatically */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define POOL_SIZE 8
/* Internal resource pool */
static char *pool[POOL_SIZE];
static int pool_initialized = 0;
/* Constructor — called automatically when library is loaded */
void __attribute__((constructor)) lib_startup(void) {
printf("[resourcelib] Constructor: initializing resource pool\n");
for (int i = 0; i < POOL_SIZE; i++) {
pool[i] = malloc(256);
if (pool[i]) {
snprintf(pool[i], 256, "slot_%d", i);
}
}
pool_initialized = 1;
printf("[resourcelib] Pool ready: %d slots allocated\n", POOL_SIZE);
}
/* Destructor — called automatically when library is unloaded */
void __attribute__((destructor)) lib_shutdown(void) {
printf("[resourcelib] Destructor: releasing resource pool\n");
for (int i = 0; i < POOL_SIZE; i++) {
free(pool[i]);
pool[i] = NULL;
}
pool_initialized = 0;
printf("[resourcelib] Pool freed\n");
}
/* Public API */
const char *pool_get(int slot) {
if (!pool_initialized || slot < 0 || slot >= POOL_SIZE) return NULL;
return pool[slot];
}
void pool_set(int slot, const char *value) {
if (!pool_initialized || slot < 0 || slot >= POOL_SIZE) return;
strncpy(pool[slot], value, 255);
pool[slot][255] = '\0';
}
/* test_resourcelib.c */
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
int main(void) {
printf("[main] Loading library...\n");
void *lib = dlopen("./resourcelib.so", RTLD_LAZY);
if (!lib) { fprintf(stderr, "%s\n", dlerror()); exit(1); }
const char *(*get_fn)(int);
void (*set_fn)(int, const char *);
dlerror();
*(void **)(&get_fn) = dlsym(lib, "pool_get");
*(void **)(&set_fn) = dlsym(lib, "pool_set");
if (!dlerror()) {
printf("[main] pool slot 0: %s\n", get_fn(0));
set_fn(2, "modified_by_main");
printf("[main] pool slot 2: %s\n", get_fn(2));
}
printf("[main] Unloading library...\n");
dlclose(lib);
printf("[main] Library unloaded\n");
return 0;
}
gcc -fPIC -shared -o resourcelib.so resourcelib.c
gcc test_resourcelib.c -ldl -o test_resourcelib
./test_resourcelib
# Output:
# [main] Loading library...
# [resourcelib] Constructor: initializing resource pool
# [resourcelib] Pool ready: 8 slots allocated
# [main] pool slot 0: slot_0
# [main] pool slot 2: modified_by_main
# [main] Unloading library...
# [resourcelib] Destructor: releasing resource pool
# [resourcelib] Pool freed
# [main] Library unloaded
Code Example 2: Multiple Constructors with Priority Ordering
/* multi_init.c — demonstrates multiple init functions and their order */
#include <stdio.h>
/* These run in order of ASCENDING priority number at load time */
void __attribute__((constructor(101))) setup_config(void) {
printf("[INIT-101] setup_config: loading configuration\n");
}
void __attribute__((constructor(102))) setup_logging(void) {
printf("[INIT-102] setup_logging: opening log file\n");
}
void __attribute__((constructor(103))) setup_network(void) {
printf("[INIT-103] setup_network: connecting to server\n");
}
/* These run in order of DESCENDING priority number at unload time */
void __attribute__((destructor(103))) teardown_network(void) {
printf("[FINI-103] teardown_network: disconnecting\n");
}
void __attribute__((destructor(102))) teardown_logging(void) {
printf("[FINI-102] teardown_logging: closing log file\n");
}
void __attribute__((destructor(101))) teardown_config(void) {
printf("[FINI-101] teardown_config: releasing config\n");
}
/* Public function */
void library_action(void) {
printf("[LIB] performing action\n");
}
gcc -fPIC -shared -o multi_init.so multi_init.c
/* Test with a simple loader */
gcc -e main -x c - -ldl <<'EOF'
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
int main(void) {
void *lib = dlopen("./multi_init.so", RTLD_LAZY);
void (*action)(void);
*(void **)(&action) = dlsym(lib, "library_action");
if (!dlerror()) (*action)();
dlclose(lib);
return 0;
}
EOF
# Expected output:
# [INIT-101] setup_config: loading configuration
# [INIT-102] setup_logging: opening log file
# [INIT-103] setup_network: connecting to server
# [LIB] performing action
# [FINI-103] teardown_network: disconnecting
# [FINI-102] teardown_logging: closing log file
# [FINI-101] teardown_config: releasing config
Code Example 3: Constructor/Destructor in the Main Program
/* main_with_ctors.c — constructor and destructor work in main programs too! */
#include <stdio.h>
#include <stdlib.h>
static int *global_array = NULL;
static int array_size = 0;
/* Runs before main() */
void __attribute__((constructor)) program_setup(void) {
printf("[CTOR] program_setup: allocating global array\n");
array_size = 10;
global_array = malloc(array_size * sizeof(int));
for (int i = 0; i < array_size; i++) {
global_array[i] = i * i;
}
}
/* Runs after main() returns (or exit() is called) */
void __attribute__((destructor)) program_teardown(void) {
printf("[DTOR] program_teardown: freeing global array\n");
free(global_array);
global_array = NULL;
}
int main(void) {
printf("[MAIN] Global array contents:\n");
for (int i = 0; i < array_size; i++) {
printf(" [%d] = %d\n", i, global_array[i]);
}
printf("[MAIN] Returning from main()\n");
return 0;
}
gcc main_with_ctors.c -o main_with_ctors
./main_with_ctors
# Output:
# [CTOR] program_setup: allocating global array
# [MAIN] Global array contents:
# [0] = 0
# [1] = 1
# [2] = 4
# ... (through [9] = 81)
# [MAIN] Returning from main()
# [DTOR] program_teardown: freeing global array
