Libraries are the building blocks of software development, enabling code reuse, modular design, and efficient distribution. In C, libraries come in two forms: static libraries (.a) and shared libraries (.so, .dylib, .dll). This comprehensive guide covers everything from basic library creation to advanced techniques like symbol visibility, versioning, and cross-platform development.
Understanding C Libraries
Static Libraries (.a on Unix, .lib on Windows)
- Compiled code is copied into the executable at link time
- No runtime dependencies on the library
- Larger executable size
- Each program gets its own copy
Shared Libraries (.so on Linux, .dylib on macOS, .dll on Windows)
- Loaded at runtime from a single location
- Smaller executable size
- Multiple programs share the same library in memory
- Runtime dependency on the library being present
Static Library Flow: ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Source │────▶│ Compiler │────▶│ Object │ │ Files │ │ (gcc -c) │ │ Files │ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Executable │◀────│ Linker │◀────│ Archiver │ │ with lib │ │ (gcc) │ │ (ar) │ └─────────────┘ └─────────────┘ └─────────────┘ Shared Library Flow: ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Source │────▶│ Compiler │────▶│ Position- │ │ Files │ │ (gcc -c) │ │ Independent│ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Executable │ │ Runtime │ │ Shared │ │ with stub │────▶│ Loader │◀────│ Library │ └─────────────┘ └─────────────┘ └─────────────┘
Creating a Simple Library
1. Library Source Files
// mathlib.h - Public header
#ifndef MATHLIB_H
#define MATHLIB_H
#ifdef __cplusplus
extern "C" {
#endif
// Library version
#define MATHLIB_VERSION_MAJOR 1
#define MATHLIB_VERSION_MINOR 0
#define MATHLIB_VERSION_PATCH 0
// Basic mathematical functions
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b, int *remainder);
// Advanced functions
double power(double base, int exponent);
long long factorial(int n);
int is_prime(int n);
int gcd(int a, int b);
int lcm(int a, int b);
// String utilities
char* str_reverse(const char *str);
int str_is_palindrome(const char *str);
// Error codes
typedef enum {
MATHLIB_SUCCESS = 0,
MATHLIB_ERROR_DIVISION_BY_ZERO,
MATHLIB_ERROR_INVALID_ARGUMENT,
MATHLIB_ERROR_OUT_OF_MEMORY
} MathLibError;
// Error handling
MathLibError get_last_error(void);
void clear_error(void);
#ifdef __cplusplus
}
#endif
#endif // MATHLIB_H
// mathlib.c - Implementation
#include "mathlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static MathLibError last_error = MATHLIB_SUCCESS;
// Helper function to set error
static void set_error(MathLibError error) {
last_error = error;
}
MathLibError get_last_error(void) {
return last_error;
}
void clear_error(void) {
last_error = MATHLIB_SUCCESS;
}
// Basic operations
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int divide(int a, int b, int *remainder) {
if (b == 0) {
set_error(MATHLIB_ERROR_DIVISION_BY_ZERO);
return 0;
}
if (remainder) {
*remainder = a % b;
}
set_error(MATHLIB_SUCCESS);
return a / b;
}
// Power function
double power(double base, int exponent) {
if (exponent == 0) return 1.0;
double result = 1.0;
int abs_exponent = exponent < 0 ? -exponent : exponent;
for (int i = 0; i < abs_exponent; i++) {
result *= base;
}
return exponent < 0 ? 1.0 / result : result;
}
// Factorial
long long factorial(int n) {
if (n < 0) {
set_error(MATHLIB_ERROR_INVALID_ARGUMENT);
return -1;
}
if (n > 20) {
// Would overflow 64-bit integer
set_error(MATHLIB_ERROR_INVALID_ARGUMENT);
return -1;
}
long long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
set_error(MATHLIB_SUCCESS);
return result;
}
// Prime number check
int is_prime(int n) {
if (n < 2) return 0;
if (n == 2) return 1;
if (n % 2 == 0) return 0;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return 0;
}
return 1;
}
// Greatest Common Divisor (Euclidean algorithm)
int gcd(int a, int b) {
a = abs(a);
b = abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
// Least Common Multiple
int lcm(int a, int b) {
if (a == 0 || b == 0) return 0;
int g = gcd(a, b);
return abs(a / g * b);
}
// String reversal
char* str_reverse(const char *str) {
if (str == NULL) {
set_error(MATHLIB_ERROR_INVALID_ARGUMENT);
return NULL;
}
size_t len = strlen(str);
char *result = malloc(len + 1);
if (result == NULL) {
set_error(MATHLIB_ERROR_OUT_OF_MEMORY);
return NULL;
}
for (size_t i = 0; i < len; i++) {
result[i] = str[len - 1 - i];
}
result[len] = '\0';
set_error(MATHLIB_SUCCESS);
return result;
}
// Palindrome check
int str_is_palindrome(const char *str) {
if (str == NULL) {
set_error(MATHLIB_ERROR_INVALID_ARGUMENT);
return 0;
}
size_t len = strlen(str);
for (size_t i = 0; i < len / 2; i++) {
if (str[i] != str[len - 1 - i]) {
return 0;
}
}
return 1;
}
Building Static Libraries
1. Linux/Unix/macOS Static Library
# Compile object files gcc -c -Wall -Wextra -O2 -fPIC mathlib.c -o mathlib.o # Create static library archive ar rcs libmathlib.a mathlib.o # View archive contents ar t libmathlib.a # Optional: Create index for faster linking ranlib libmathlib.a # Clean up rm mathlib.o
2. Using the Static Library
// test_static.c
#include <stdio.h>
#include "mathlib.h"
int main() {
printf("=== Static Library Test ===\n\n");
// Test basic operations
printf("Basic Operations:\n");
printf(" 5 + 3 = %d\n", add(5, 3));
printf(" 5 - 3 = %d\n", subtract(5, 3));
printf(" 5 * 3 = %d\n", multiply(5, 3));
int remainder;
int quotient = divide(10, 3, &remainder);
printf(" 10 / 3 = %d remainder %d\n", quotient, remainder);
// Test advanced functions
printf("\nAdvanced Functions:\n");
printf(" 2^10 = %.0f\n", power(2.0, 10));
printf(" 10! = %lld\n", factorial(10));
printf(" 17 is prime: %s\n", is_prime(17) ? "yes" : "no");
printf(" GCD(48, 18) = %d\n", gcd(48, 18));
printf(" LCM(12, 18) = %d\n", lcm(12, 18));
// Test string utilities
printf("\nString Utilities:\n");
char *reversed = str_reverse("hello");
printf(" 'hello' reversed: '%s'\n", reversed);
printf(" 'racecar' is palindrome: %s\n",
str_is_palindrome("racecar") ? "yes" : "no");
free(reversed);
// Test error handling
printf("\nError Handling:\n");
divide(10, 0, NULL);
printf(" Division by zero error: %d\n", get_last_error());
return 0;
}
Build and run:
gcc -o test_static test_static.c -L. -lmathlib ./test_static
Building Shared Libraries
1. Linux Shared Library (.so)
# Compile position-independent code (PIC) gcc -c -Wall -Wextra -O2 -fPIC mathlib.c -o mathlib.o # Create shared library gcc -shared -Wl,-soname,libmathlib.so.1 -o libmathlib.so.1.0.0 mathlib.o # Create symbolic links for versioning ln -sf libmathlib.so.1.0.0 libmathlib.so.1 ln -sf libmathlib.so.1 libmathlib.so # Clean up rm mathlib.o
2. macOS Shared Library (.dylib)
# Compile position-independent code gcc -c -Wall -Wextra -O2 -fPIC mathlib.c -o mathlib.o # Create dynamic library gcc -dynamiclib -install_name @rpath/libmathlib.dylib \ -current_version 1.0.0 -compatibility_version 1.0 \ -o libmathlib.dylib mathlib.o # Clean up rm mathlib.o
3. Windows DLL (with MinGW)
# Compile with DLL export gcc -c -Wall -O2 -DBUILDING_DLL mathlib.c -o mathlib.o # Create DLL gcc -shared -o mathlib.dll mathlib.o -Wl,--out-implib,libmathlib.a # Clean up rm mathlib.o
Advanced Library Features
1. Symbol Visibility Control
// mathlib_visibility.h
#ifndef MATHLIB_VISIBILITY_H
#define MATHLIB_VISIBILITY_H
#if defined(_WIN32) || defined(__CYGWIN__)
#ifdef BUILDING_DLL
#define MATHLIB_API __declspec(dllexport)
#else
#define MATHLIB_API __declspec(dllimport)
#endif
#else
#if __GNUC__ >= 4
#define MATHLIB_API __attribute__((visibility("default")))
#define MATHLIB_LOCAL __attribute__((visibility("hidden")))
#else
#define MATHLIB_API
#define MATHLIB_LOCAL
#endif
#endif
// Export public symbols
MATHLIB_API int add(int a, int b);
MATHLIB_API int subtract(int a, int b);
// Hide internal symbols
MATHLIB_LOCAL void internal_helper(void);
#endif
2. Library Initialization and Cleanup
// mathlib_init.c
#include <stdio.h>
#include <stdlib.h>
static int initialization_count = 0;
static int cleanup_called = 0;
// Constructor - runs when library is loaded
__attribute__((constructor))
static void library_init(void) {
initialization_count++;
printf("Math library initialized (count: %d)\n", initialization_count);
// Initialize global resources
// Allocate static buffers
// Set up logging
}
// Destructor - runs when library is unloaded
__attribute__((destructor))
static void library_cleanup(void) {
cleanup_called = 1;
printf("Math library cleaned up\n");
// Free global resources
// Flush logs
// Close file handles
}
// Manual initialization (alternative)
int mathlib_initialize(void) {
if (initialization_count == 0) {
library_init();
}
return MATHLIB_SUCCESS;
}
int mathlib_shutdown(void) {
if (cleanup_called) {
return MATHLIB_SUCCESS;
}
library_cleanup();
return MATHLIB_SUCCESS;
}
3. Library Versioning
// mathlib_version.h
#ifndef MATHLIB_VERSION_H
#define MATHLIB_VERSION_H
#define MATHLIB_VERSION_MAJOR 1
#define MATHLIB_VERSION_MINOR 0
#define MATHLIB_VERSION_PATCH 0
#define MATHLIB_VERSION_STRING "1.0.0"
typedef struct {
int major;
int minor;
int patch;
const char *string;
} MathLibVersion;
MATHLIB_API MathLibVersion mathlib_get_version(void);
#endif
// Implementation
MATHLIB_API MathLibVersion mathlib_get_version(void) {
MathLibVersion version = {
MATHLIB_VERSION_MAJOR,
MATHLIB_VERSION_MINOR,
MATHLIB_VERSION_PATCH,
MATHLIB_VERSION_STRING
};
return version;
}
4. Thread Safety
// mathlib_threadsafe.c
#include <pthread.h>
// Thread-local storage
static __thread int tls_error;
static __thread char tls_buffer[256];
// Mutex for shared resources
static pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;
// Thread-safe error handling
void set_thread_error(MathLibError error) {
tls_error = error;
}
MathLibError get_thread_error(void) {
return tls_error;
}
// Thread-safe string reversal with buffer
char* str_reverse_threadsafe(const char *str) {
if (str == NULL) {
tls_error = MATHLIB_ERROR_INVALID_ARGUMENT;
return NULL;
}
size_t len = strlen(str);
if (len >= sizeof(tls_buffer)) {
tls_error = MATHLIB_ERROR_INVALID_ARGUMENT;
return NULL;
}
for (size_t i = 0; i < len; i++) {
tls_buffer[i] = str[len - 1 - i];
}
tls_buffer[len] = '\0';
tls_error = MATHLIB_SUCCESS;
return tls_buffer;
}
// Thread-safe with mutex for shared resource
int increment_global_counter(void) {
static int global_counter = 0;
int result;
pthread_mutex_lock(&global_mutex);
result = ++global_counter;
pthread_mutex_unlock(&global_mutex);
return result;
}
Library Project Structure
mathlib/ ├── include/ │ └── mathlib/ │ ├── mathlib.h │ ├── mathlib_version.h │ └── mathlib_visibility.h ├── src/ │ ├── mathlib.c │ ├── mathlib_init.c │ ├── mathlib_threadsafe.c │ └── mathlib_private.h ├── tests/ │ ├── test_mathlib.c │ ├── test_mathlib_threadsafe.c │ └── test_runner.c ├── examples/ │ ├── example_basic.c │ └── example_advanced.c ├── docs/ │ ├── Doxyfile │ └── README.md ├── CMakeLists.txt ├── Makefile └── LICENSE
CMake Build System
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mathlib VERSION 1.0.0 LANGUAGES C)
# Set C standard
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Library options
option(BUILD_SHARED_LIBS "Build shared library" ON)
option(BUILD_TESTS "Build tests" ON)
option(BUILD_EXAMPLES "Build examples" ON)
# Include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
# Source files
set(SOURCES
src/mathlib.c
src/mathlib_init.c
src/mathlib_threadsafe.c
)
# Create library
add_library(mathlib ${SOURCES})
# Set library properties
set_target_properties(mathlib PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
# Install targets
install(TARGETS mathlib
EXPORT mathlibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
# Install headers
install(DIRECTORY include/ DESTINATION include)
# Export package
install(EXPORT mathlibTargets
FILE mathlibTargets.cmake
NAMESPACE mathlib::
DESTINATION lib/cmake/mathlib
)
# Tests
if(BUILD_TESTS)
enable_testing()
add_executable(test_mathlib tests/test_mathlib.c)
target_link_libraries(test_mathlib mathlib)
add_test(NAME mathlib_test COMMAND test_mathlib)
endif()
# Examples
if(BUILD_EXAMPLES)
add_executable(example_basic examples/example_basic.c)
target_link_libraries(example_basic mathlib)
endif()
Makefile Build System
# Makefile
CC = gcc
CFLAGS = -Wall -Wextra -O2 -fPIC
LDFLAGS = -lm
# Library version
VERSION = 1.0.0
SONAME = libmathlib.so.1
NAME = libmathlib.so.$(VERSION)
# Directories
SRCDIR = src
INCDIR = include
OBJDIR = obj
LIBDIR = lib
# Source files
SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
# Targets
.PHONY: all clean install uninstall test examples
all: static shared
static: $(LIBDIR)/libmathlib.a
shared: $(LIBDIR)/$(NAME)
# Static library
$(LIBDIR)/libmathlib.a: $(OBJECTS)
@mkdir -p $(LIBDIR)
ar rcs $@ $^
ranlib $@
# Shared library
$(LIBDIR)/$(NAME): $(OBJECTS)
@mkdir -p $(LIBDIR)
$(CC) -shared -Wl,-soname,$(SONAME) -o $@ $^ $(LDFLAGS)
ln -sf $(NAME) $(LIBDIR)/$(SONAME)
ln -sf $(NAME) $(LIBDIR)/libmathlib.so
# Object files
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -I$(INCDIR) -c $< -o $@
# Test program
test: shared
$(CC) $(CFLAGS) -I$(INCDIR) -L$(LIBDIR) tests/test_mathlib.c -o test_mathlib -lmathlib
LD_LIBRARY_PATH=$(LIBDIR) ./test_mathlib
# Examples
examples: shared
for example in examples/*.c; do \
$(CC) $(CFLAGS) -I$(INCDIR) -L$(LIBDIR) $$example -o $${example%.c} -lmathlib; \
done
# Install
install: all
install -d $(DESTDIR)/usr/local/lib
install -d $(DESTDIR)/usr/local/include/mathlib
install -m 755 $(LIBDIR)/libmathlib.so* $(DESTDIR)/usr/local/lib/
install -m 644 $(INCDIR)/mathlib/*.h $(DESTDIR)/usr/local/include/mathlib/
ldconfig
# Uninstall
uninstall:
rm -f $(DESTDIR)/usr/local/lib/libmathlib.so*
rm -rf $(DESTDIR)/usr/local/include/mathlib
# Clean
clean:
rm -rf $(OBJDIR) $(LIBDIR)
rm -f test_mathlib examples/example_*
Testing the Library
// tests/test_mathlib.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "mathlib.h"
#define TEST_ASSERT(cond, msg) \
do { \
if (!(cond)) { \
fprintf(stderr, "FAILED: %s\n", msg); \
return 1; \
} \
} while(0)
int test_basic_operations(void) {
TEST_ASSERT(add(5, 3) == 8, "add(5,3) != 8");
TEST_ASSERT(subtract(5, 3) == 2, "subtract(5,3) != 2");
TEST_ASSERT(multiply(5, 3) == 15, "multiply(5,3) != 15");
int remainder;
int quotient = divide(10, 3, &remainder);
TEST_ASSERT(quotient == 3, "divide(10,3) quotient != 3");
TEST_ASSERT(remainder == 1, "divide(10,3) remainder != 1");
return 0;
}
int test_advanced_functions(void) {
TEST_ASSERT(power(2.0, 10) == 1024.0, "power(2,10) != 1024");
TEST_ASSERT(factorial(5) == 120, "factorial(5) != 120");
TEST_ASSERT(is_prime(17) == 1, "17 is prime");
TEST_ASSERT(is_prime(4) == 0, "4 is not prime");
TEST_ASSERT(gcd(48, 18) == 6, "gcd(48,18) != 6");
TEST_ASSERT(lcm(12, 18) == 36, "lcm(12,18) != 36");
return 0;
}
int test_string_utilities(void) {
char *reversed = str_reverse("hello");
TEST_ASSERT(reversed != NULL, "str_reverse failed");
TEST_ASSERT(strcmp(reversed, "olleh") == 0, "reverse 'hello' != 'olleh'");
free(reversed);
TEST_ASSERT(str_is_palindrome("racecar") == 1, "'racecar' is palindrome");
TEST_ASSERT(str_is_palindrome("hello") == 0, "'hello' is not palindrome");
return 0;
}
int test_error_handling(void) {
clear_error();
divide(10, 0, NULL);
TEST_ASSERT(get_last_error() == MATHLIB_ERROR_DIVISION_BY_ZERO,
"Division by zero error not set");
clear_error();
factorial(-1);
TEST_ASSERT(get_last_error() == MATHLIB_ERROR_INVALID_ARGUMENT,
"Invalid argument error not set");
return 0;
}
int main(void) {
int failed = 0;
printf("Running tests...\n\n");
printf("Testing basic operations... ");
failed |= test_basic_operations();
printf("done\n");
printf("Testing advanced functions... ");
failed |= test_advanced_functions();
printf("done\n");
printf("Testing string utilities... ");
failed |= test_string_utilities();
printf("done\n");
printf("Testing error handling... ");
failed |= test_error_handling();
printf("done\n");
if (failed) {
printf("\n*** TESTS FAILED ***\n");
return 1;
} else {
printf("\n*** ALL TESTS PASSED ***\n");
return 0;
}
}
Documentation with Doxygen
/**
* @file mathlib.h
* @brief Mathematical utility library
* @author Your Name
* @version 1.0.0
*/
/**
* @defgroup mathlib Math Library
* @brief Core mathematical operations
* @{
*/
/**
* @brief Adds two integers
* @param a First operand
* @param b Second operand
* @return Sum of a and b
*/
int add(int a, int b);
/**
* @brief Divides two integers
* @param a Dividend
* @param b Divisor (must not be zero)
* @param remainder Pointer to store remainder (can be NULL)
* @return Quotient of a divided by b
* @retval 0 Division by zero (error set)
* @note Sets error MATHLIB_ERROR_DIVISION_BY_ZERO on failure
*/
int divide(int a, int b, int *remainder);
/**
* @brief Computes factorial
* @param n Non-negative integer (max 20)
* @return n! or -1 on error
* @retval -1 Invalid argument (n < 0 or n > 20)
*/
long long factorial(int n);
/** @} */
Best Practices
- Header Guards: Always use include guards
- API Stability: Maintain backward compatibility
- Documentation: Document public API thoroughly
- Error Handling: Provide consistent error reporting
- Thread Safety: Document thread safety guarantees
- Versioning: Use semantic versioning
- Testing: Include comprehensive test suite
- Build Systems: Support both Make and CMake
- Cross-Platform: Handle Windows, Linux, macOS differences
- Memory Management: Clearly document ownership
Conclusion
Creating C libraries is a fundamental skill for systems programming. This guide covered:
- Static vs shared libraries and their trade-offs
- Building libraries with Make and CMake
- Symbol visibility and API design
- Thread safety and error handling
- Testing and documentation
- Cross-platform considerations
Well-designed libraries are the foundation of reusable, maintainable software. By following the practices outlined here, you can create professional-quality C libraries that are reliable, well-documented, and easy to integrate into other projects.