Claude-skill-registry lang-c-library-dev
C library development patterns covering API design, header organization, memory management for libraries, ABI stability, build system integration, documentation with Doxygen, testing frameworks, and packaging. Use when creating C libraries, designing public APIs, managing build systems (CMake, Make, Meson), or distributing C packages. Extends lang-c-dev with library-specific tooling and patterns.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lang-c-library-dev" ~/.claude/skills/majiayu000-claude-skill-registry-lang-c-library-dev && rm -rf "$T"
skills/data/lang-c-library-dev/SKILL.mdC Library Development
C-specific patterns for library development. This skill extends
lang-c-dev with library design patterns, build systems, ABI stability, and packaging practices.
This Skill Extends
- Foundational C programming (type system, memory, pointers, preprocessor)lang-c-dev
For general concepts like type system, pointers, memory management basics, and preprocessor directives, see the foundational skill first.
This Skill Adds
- Library design: API design patterns, header organization, ABI stability
- Build systems: CMake, Make, Meson, Autotools, pkg-config
- Documentation: Doxygen, man pages, API documentation
- Testing: Unity, Check, CUnit, Criterion testing frameworks
- Packaging: Static/shared libraries, versioning, distribution
This Skill Does NOT Cover
- General C programming - see
lang-c-dev - Advanced memory engineering - see
lang-c-memory-eng - POSIX APIs and system calls - see
lang-c-posix-dev - Systems programming patterns - see
lang-c-systems-eng - Embedded programming - see
lang-c-embedded-dev
Quick Reference
| Task | Command/Pattern |
|---|---|
| Create static library | |
| Create shared library | |
| Install library | or |
| Generate docs | |
| Run tests | or |
| Check ABI | |
| Package config | |
Library Design Patterns
Opaque Pointer Pattern (PIMPL)
Purpose: Hide implementation details, maintain ABI stability
// mylib.h (public API) #ifndef MYLIB_H #define MYLIB_H #include <stddef.h> // Opaque pointer - users cannot see internal structure typedef struct mylib_context mylib_context_t; // Constructor/destructor mylib_context_t* mylib_create(void); void mylib_destroy(mylib_context_t* ctx); // Operations int mylib_process(mylib_context_t* ctx, const char* input, char* output, size_t output_size); int mylib_set_option(mylib_context_t* ctx, const char* key, const char* value); #endif // MYLIB_H
// mylib.c (implementation) #include "mylib.h" #include <stdlib.h> #include <string.h> // Full structure definition hidden from users struct mylib_context { char* buffer; size_t buffer_size; int flags; void* internal_state; }; mylib_context_t* mylib_create(void) { mylib_context_t* ctx = calloc(1, sizeof(*ctx)); if (!ctx) return NULL; ctx->buffer_size = 4096; ctx->buffer = malloc(ctx->buffer_size); if (!ctx->buffer) { free(ctx); return NULL; } return ctx; } void mylib_destroy(mylib_context_t* ctx) { if (ctx) { free(ctx->buffer); free(ctx); } } int mylib_process(mylib_context_t* ctx, const char* input, char* output, size_t output_size) { if (!ctx || !input || !output) return -1; // Implementation... return 0; }
Error Handling Patterns
Return codes with error context:
// error.h #ifndef MYLIB_ERROR_H #define MYLIB_ERROR_H // Error codes typedef enum { MYLIB_OK = 0, MYLIB_ERR_INVALID_ARGUMENT = -1, MYLIB_ERR_OUT_OF_MEMORY = -2, MYLIB_ERR_IO = -3, MYLIB_ERR_PARSE = -4, } mylib_error_t; // Get human-readable error message const char* mylib_strerror(mylib_error_t error); // Get last error for a context (thread-safe) mylib_error_t mylib_last_error(mylib_context_t* ctx); void mylib_clear_error(mylib_context_t* ctx); #endif
// error.c #include "error.h" const char* mylib_strerror(mylib_error_t error) { switch (error) { case MYLIB_OK: return "Success"; case MYLIB_ERR_INVALID_ARGUMENT: return "Invalid argument"; case MYLIB_ERR_OUT_OF_MEMORY: return "Out of memory"; case MYLIB_ERR_IO: return "I/O error"; case MYLIB_ERR_PARSE: return "Parse error"; default: return "Unknown error"; } }
Memory Management for Libraries
Rule 1: Who allocates, who frees
// Pattern 1: Library allocates and frees typedef struct mylib_result mylib_result_t; mylib_result_t* mylib_compute(const char* input); void mylib_result_free(mylib_result_t* result); // Pattern 2: Caller allocates, library fills int mylib_compute_inplace(const char* input, char* output, size_t* output_size); // Pattern 3: Callback for custom allocation typedef void* (*mylib_alloc_fn)(size_t size); typedef void (*mylib_free_fn)(void* ptr); void mylib_set_allocator(mylib_alloc_fn alloc, mylib_free_fn free);
Pattern 2 example:
int mylib_get_info(mylib_context_t* ctx, char* buffer, size_t* buffer_size) { if (!ctx || !buffer_size) return MYLIB_ERR_INVALID_ARGUMENT; // Get required size size_t required = calculate_info_size(ctx); // If buffer is NULL or too small, return required size if (!buffer || *buffer_size < required) { *buffer_size = required; return MYLIB_ERR_INVALID_ARGUMENT; } // Fill buffer fill_info(ctx, buffer, *buffer_size); *buffer_size = required; return MYLIB_OK; }
Namespace Prefixing
All public symbols must be prefixed:
// Good: All symbols prefixed with mylib_ mylib_context_t* mylib_create(void); typedef enum { MYLIB_OK, MYLIB_ERR } mylib_error_t; #define MYLIB_VERSION "1.0.0" // Bad: Pollutes global namespace context_t* create(void); typedef enum { OK, ERR } error_t; #define VERSION "1.0.0"
Header File Organization
Public vs Private Headers
include/ ├── mylib/ # Public API │ ├── mylib.h # Main header │ ├── error.h # Error types │ └── types.h # Public types src/ ├── internal.h # Private API ├── mylib.c ├── error.c └── internal.c
Header Best Practices
// mylib.h #ifndef MYLIB_H #define MYLIB_H // Include guards always (or #pragma once) // System headers first #include <stddef.h> #include <stdint.h> // Library version #define MYLIB_VERSION_MAJOR 1 #define MYLIB_VERSION_MINOR 2 #define MYLIB_VERSION_PATCH 3 // Visibility macros #ifdef _WIN32 #ifdef MYLIB_BUILDING #define MYLIB_API __declspec(dllexport) #else #define MYLIB_API __declspec(dllimport) #endif #else #define MYLIB_API __attribute__((visibility("default"))) #endif // C++ compatibility #ifdef __cplusplus extern "C" { #endif // Forward declarations typedef struct mylib_context mylib_context_t; // API declarations MYLIB_API mylib_context_t* mylib_create(void); MYLIB_API void mylib_destroy(mylib_context_t* ctx); #ifdef __cplusplus } #endif #endif // MYLIB_H
Feature Detection Headers
// config.h (generated by build system) #ifndef MYLIB_CONFIG_H #define MYLIB_CONFIG_H // Feature detection #define MYLIB_HAVE_THREADS 1 #define MYLIB_HAVE_ZLIB 1 // #undef MYLIB_HAVE_OPENSSL // Platform detection #ifdef _WIN32 #define MYLIB_PLATFORM_WINDOWS #elif defined(__APPLE__) #define MYLIB_PLATFORM_MACOS #elif defined(__linux__) #define MYLIB_PLATFORM_LINUX #endif #endif
ABI Stability
Versioning Strategy
Semantic Versioning for ABI:
- Major: Breaking ABI changes
- Minor: New features, ABI compatible
- Patch: Bug fixes, ABI compatible
ABI-Safe Changes
Safe (ABI compatible):
- Adding new functions
- Adding new struct members at the end (if opaque)
- Adding new enum values (if used with non_exhaustive pattern)
- Increasing struct size (if opaque)
Unsafe (ABI breaking):
- Removing functions
- Changing function signatures
- Reordering struct members
- Changing struct sizes (if exposed)
- Changing enum underlying type
Symbol Versioning
// Use symbol versioning for evolving APIs __asm__(".symver mylib_open_v1, mylib_open@MYLIB_1.0"); __asm__(".symver mylib_open_v2, mylib_open@@MYLIB_2.0"); // Old version int mylib_open_v1(const char* path); // New version (default) int mylib_open_v2(const char* path, int flags);
Version Script (Linux)
# mylib.map MYLIB_1.0 { global: mylib_create; mylib_destroy; mylib_process; local: *; }; MYLIB_2.0 { global: mylib_process_v2; } MYLIB_1.0;
# Link with version script gcc -shared -Wl,--version-script=mylib.map -o libmylib.so *.o
Build Systems
CMake (Modern Approach)
# CMakeLists.txt cmake_minimum_required(VERSION 3.15) project(mylib VERSION 1.2.3 LANGUAGES C) # Options option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(MYLIB_BUILD_TESTS "Build tests" ON) option(MYLIB_BUILD_DOCS "Build documentation" OFF) # Library target add_library(mylib src/mylib.c src/error.c src/internal.c ) # Include directories target_include_directories(mylib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ) # Compiler features target_compile_features(mylib PRIVATE c_std_99) target_compile_options(mylib PRIVATE $<$<C_COMPILER_ID:GNU,Clang>:-Wall -Wextra -pedantic> $<$<C_COMPILER_ID:MSVC>:/W4> ) # Symbol visibility set_target_properties(mylib PROPERTIES C_VISIBILITY_PRESET hidden VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) # Dependencies find_package(ZLIB) if(ZLIB_FOUND) target_link_libraries(mylib PRIVATE ZLIB::ZLIB) target_compile_definitions(mylib PRIVATE MYLIB_HAVE_ZLIB) endif() # Installation include(GNUInstallDirs) install(TARGETS mylib EXPORT mylibTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(DIRECTORY include/mylib DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # Export targets install(EXPORT mylibTargets FILE mylibTargets.cmake NAMESPACE mylib:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib ) # Generate config files include(CMakePackageConfigHelpers) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/mylibConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/mylibConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mylibConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib ) # Testing if(MYLIB_BUILD_TESTS) enable_testing() add_subdirectory(tests) endif()
Makefile (Traditional Approach)
# Makefile PREFIX ?= /usr/local LIBDIR ?= $(PREFIX)/lib INCLUDEDIR ?= $(PREFIX)/include CC = gcc CFLAGS = -Wall -Wextra -std=c99 -O2 -fPIC LDFLAGS = -shared # Library name and version LIB_NAME = mylib LIB_VERSION = 1.2.3 LIB_SOVERSION = 1 # Source files SRCS = src/mylib.c src/error.c src/internal.c OBJS = $(SRCS:.c=.o) HEADERS = include/mylib/mylib.h include/mylib/error.h # Targets STATIC_LIB = lib$(LIB_NAME).a SHARED_LIB = lib$(LIB_NAME).so.$(LIB_VERSION) SHARED_LIB_LINK = lib$(LIB_NAME).so SHARED_LIB_SONAME = lib$(LIB_NAME).so.$(LIB_SOVERSION) .PHONY: all clean install uninstall test all: $(STATIC_LIB) $(SHARED_LIB) # Static library $(STATIC_LIB): $(OBJS) ar rcs $@ $^ # Shared library $(SHARED_LIB): $(OBJS) $(CC) $(LDFLAGS) -Wl,-soname,$(SHARED_LIB_SONAME) -o $@ $^ ln -sf $(SHARED_LIB) $(SHARED_LIB_SONAME) ln -sf $(SHARED_LIB_SONAME) $(SHARED_LIB_LINK) # Object files %.o: %.c $(CC) $(CFLAGS) -Iinclude -c -o $@ $< # Install install: all install -d $(DESTDIR)$(LIBDIR) install -m 644 $(STATIC_LIB) $(DESTDIR)$(LIBDIR)/ install -m 755 $(SHARED_LIB) $(DESTDIR)$(LIBDIR)/ ln -sf $(SHARED_LIB) $(DESTDIR)$(LIBDIR)/$(SHARED_LIB_SONAME) ln -sf $(SHARED_LIB_SONAME) $(DESTDIR)$(LIBDIR)/$(SHARED_LIB_LINK) install -d $(DESTDIR)$(INCLUDEDIR)/mylib install -m 644 $(HEADERS) $(DESTDIR)$(INCLUDEDIR)/mylib/ ldconfig -n $(DESTDIR)$(LIBDIR) # Uninstall uninstall: rm -f $(DESTDIR)$(LIBDIR)/$(STATIC_LIB) rm -f $(DESTDIR)$(LIBDIR)/$(SHARED_LIB)* rm -rf $(DESTDIR)$(INCLUDEDIR)/mylib # Clean clean: rm -f $(OBJS) $(STATIC_LIB) $(SHARED_LIB)* # Tests test: $(MAKE) -C tests
Meson (Modern Alternative)
# meson.build project('mylib', 'c', version: '1.2.3', default_options: ['c_std=c99', 'warning_level=3'] ) # Dependencies zlib_dep = dependency('zlib', required: false) # Configuration conf_data = configuration_data() conf_data.set('MYLIB_VERSION', meson.project_version()) conf_data.set('MYLIB_HAVE_ZLIB', zlib_dep.found()) configure_file( input: 'config.h.in', output: 'config.h', configuration: conf_data ) # Library mylib_sources = files( 'src/mylib.c', 'src/error.c', 'src/internal.c' ) mylib_inc = include_directories('include') mylib = library('mylib', mylib_sources, include_directories: mylib_inc, dependencies: zlib_dep, version: meson.project_version(), soversion: '1', install: true ) # Install headers install_headers( 'include/mylib/mylib.h', 'include/mylib/error.h', subdir: 'mylib' ) # pkg-config pkg = import('pkgconfig') pkg.generate(mylib, description: 'My awesome C library', subdirs: 'mylib' ) # Testing if get_option('build_tests') subdir('tests') endif
pkg-config File
# mylib.pc.in prefix=@PREFIX@ exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include Name: mylib Description: My awesome C library Version: @VERSION@ Libs: -L${libdir} -lmylib Cflags: -I${includedir} Requires: zlib
Documentation with Doxygen
Doxygen Configuration
# Doxyfile PROJECT_NAME = "mylib" PROJECT_NUMBER = 1.2.3 OUTPUT_DIRECTORY = docs GENERATE_HTML = YES GENERATE_LATEX = NO EXTRACT_ALL = YES EXTRACT_PRIVATE = NO EXTRACT_STATIC = NO INPUT = include/mylib RECURSIVE = YES USE_MDFILE_AS_MAINPAGE = README.md
Documentation Patterns
/** * @file mylib.h * @brief Main library interface * @author Your Name */ /** * @brief Context object for library operations * * This is an opaque type. Users should not access its members directly. * Use the provided API functions to interact with it. */ typedef struct mylib_context mylib_context_t; /** * @brief Create a new library context * * @return Pointer to new context, or NULL on allocation failure * @note The returned context must be freed with mylib_destroy() * @see mylib_destroy() */ MYLIB_API mylib_context_t* mylib_create(void); /** * @brief Destroy a library context * * @param ctx Context to destroy (may be NULL) * @note Safe to call with NULL pointer * @warning Do not use the context after calling this function */ MYLIB_API void mylib_destroy(mylib_context_t* ctx); /** * @brief Process input data * * @param ctx Library context * @param input Input string (must be null-terminated) * @param output Output buffer * @param output_size Size of output buffer * @return 0 on success, negative error code on failure * @retval MYLIB_OK Success * @retval MYLIB_ERR_INVALID_ARGUMENT Invalid parameter * @retval MYLIB_ERR_OUT_OF_MEMORY Allocation failed * * @code{.c} * mylib_context_t* ctx = mylib_create(); * char output[1024]; * int result = mylib_process(ctx, "input", output, sizeof(output)); * if (result == MYLIB_OK) { * printf("Output: %s\n", output); * } * mylib_destroy(ctx); * @endcode */ MYLIB_API int mylib_process(mylib_context_t* ctx, const char* input, char* output, size_t output_size);
Testing Frameworks
Unity Test Framework
// test_mylib.c #include "unity.h" #include "mylib/mylib.h" void setUp(void) { // Run before each test } void tearDown(void) { // Run after each test } void test_create_destroy(void) { mylib_context_t* ctx = mylib_create(); TEST_ASSERT_NOT_NULL(ctx); mylib_destroy(ctx); } void test_process_valid_input(void) { mylib_context_t* ctx = mylib_create(); char output[1024]; int result = mylib_process(ctx, "test input", output, sizeof(output)); TEST_ASSERT_EQUAL(MYLIB_OK, result); TEST_ASSERT_EQUAL_STRING("expected output", output); mylib_destroy(ctx); } void test_process_invalid_argument(void) { int result = mylib_process(NULL, "input", NULL, 0); TEST_ASSERT_EQUAL(MYLIB_ERR_INVALID_ARGUMENT, result); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_create_destroy); RUN_TEST(test_process_valid_input); RUN_TEST(test_process_invalid_argument); return UNITY_END(); }
Check Framework
// test_check.c #include <check.h> #include "mylib/mylib.h" START_TEST(test_create) { mylib_context_t* ctx = mylib_create(); ck_assert_ptr_nonnull(ctx); mylib_destroy(ctx); } END_TEST START_TEST(test_process) { mylib_context_t* ctx = mylib_create(); char output[1024]; int result = mylib_process(ctx, "input", output, sizeof(output)); ck_assert_int_eq(result, MYLIB_OK); mylib_destroy(ctx); } END_TEST Suite* mylib_suite(void) { Suite* s = suite_create("mylib"); TCase* tc_core = tcase_create("Core"); tcase_add_test(tc_core, test_create); tcase_add_test(tc_core, test_process); suite_add_tcase(s, tc_core); return s; } int main(void) { int number_failed; Suite* s = mylib_suite(); SRunner* sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? 0 : 1; }
Criterion (Modern Framework)
// test_criterion.c #include <criterion/criterion.h> #include "mylib/mylib.h" Test(mylib, create_destroy) { mylib_context_t* ctx = mylib_create(); cr_assert_not_null(ctx); mylib_destroy(ctx); } Test(mylib, process_valid) { mylib_context_t* ctx = mylib_create(); char output[1024]; int result = mylib_process(ctx, "input", output, sizeof(output)); cr_assert_eq(result, MYLIB_OK); mylib_destroy(ctx); } Test(mylib, process_null_context) { char output[1024]; int result = mylib_process(NULL, "input", output, sizeof(output)); cr_assert_eq(result, MYLIB_ERR_INVALID_ARGUMENT); }
Packaging and Distribution
Static vs Shared Libraries
Static Library (.a):
# Compile object files gcc -c -I include src/mylib.c -o mylib.o gcc -c -I include src/error.c -o error.o # Create archive ar rcs libmylib.a mylib.o error.o # Link with static library gcc main.c -L. -lmylib -o program
Shared Library (.so / .dylib / .dll):
# Compile with position-independent code gcc -fPIC -c -I include src/mylib.c -o mylib.o gcc -fPIC -c -I include src/error.c -o error.o # Create shared library (Linux) gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.2.3 mylib.o error.o # Create symlinks ln -s libmylib.so.1.2.3 libmylib.so.1 ln -s libmylib.so.1 libmylib.so # Link with shared library gcc main.c -L. -lmylib -o program
Versioning Scheme
Linux:
- Real file with full versionlibmylib.so.1.2.3
- Symlink (SONAME) for ABI compatibilitylibmylib.so.1
- Symlink for linkerlibmylib.so
macOS:
- Real filelibmylib.1.2.3.dylib
- Symlinklibmylib.1.dylib
- Symlinklibmylib.dylib
Distribution Packages
Debian/Ubuntu (.deb):
# Structure mylib_1.2.3/ ├── debian/ │ ├── control │ ├── rules │ ├── changelog │ └── copyright ├── include/ ├── src/ └── CMakeLists.txt # Build package dpkg-buildpackage -us -uc
Red Hat/Fedora (.rpm):
# mylib.spec Name: mylib Version: 1.2.3 Release: 1%{?dist} Summary: My awesome C library License: MIT URL: https://github.com/username/mylib Source0: %{name}-%{version}.tar.gz BuildRequires: gcc, cmake, zlib-devel Requires: zlib %description My awesome C library for doing awesome things. %prep %setup -q %build %cmake %cmake_build %install %cmake_install %files %license LICENSE %doc README.md %{_libdir}/libmylib.so.* %{_libdir}/libmylib.a %{_includedir}/mylib/ %changelog * Mon Jan 01 2024 Your Name <email@example.com> - 1.2.3-1 - Initial package
Best Practices Checklist
- All public symbols have library prefix (
)mylib_ - Header files have include guards or
#pragma once - C++ compatibility with
extern "C" - Symbol visibility controlled (hidden by default)
- Opaque pointers for implementation hiding
- Clear memory ownership rules
- Consistent error handling pattern
- Thread-safety documented
- ABI versioning strategy
- Comprehensive API documentation
- Unit tests with good coverage
- pkg-config file provided
- CMake config files generated
- Build works on multiple platforms
- No warnings with
-Wall -Wextra -pedantic
Common Pitfalls
1. Exposing Internal Types
// Bad: Exposes internal implementation typedef struct { char* buffer; size_t size; } mylib_context_t; // Good: Opaque pointer typedef struct mylib_context mylib_context_t;
2. No Namespace Prefixing
// Bad: Pollutes global namespace int init(void); void cleanup(void); // Good: Prefixed int mylib_init(void); void mylib_cleanup(void);
3. Unclear Memory Ownership
// Bad: Who frees the result? char* mylib_process(const char* input); // Good: Clear ownership char* mylib_process(const char* input); // Caller frees void mylib_result_free(char* result);
4. Breaking ABI
// v1.0.0 int mylib_func(int a); // v1.1.0 - WRONG! ABI break int mylib_func(int a, int b); // v1.1.0 - Correct: Add new function int mylib_func(int a); int mylib_func_ex(int a, int b);
References
- Foundational C programminglang-c-dev
- Advanced memory patternslang-c-memory-eng- CMake Documentation
- Meson Build System
- Doxygen Manual
- Unity Test Framework
- Check Framework
- Criterion
- pkg-config Guide