lang-c-library-dev

aRustyDev's avatarfrom aRustyDev

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.

⭐1stars🔀1forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill provides comprehensive patterns and best practices for professional C library development. It covers critical architectural aspects such as robust API design using opaque pointers (PIMPL), header organization, and memory management strategies. Additionally, it offers deep integration guidance for build systems like CMake and Meson, ABI stability maintenance, Doxygen documentation, and various testing frameworks to ensure the creation of high-quality, distributable C packages.

Use Cases

  • Designing a public C API that hides implementation details to ensure long-term ABI compatibility and minimize breaking changes.
  • Setting up modern, cross-platform build systems using CMake, Meson, or traditional Makefiles for complex C projects.
  • Implementing professional memory management patterns and error handling strategies within a shared library context.
  • Automating technical documentation with Doxygen and establishing unit testing workflows using frameworks like Unity, Check, or Criterion.
  • Preparing C libraries for distribution by managing symbol visibility, versioning, and creating Linux (.deb, .rpm) or macOS packages.
namelang-c-library-dev
descriptionC 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.

C 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

  • lang-c-dev - Foundational C programming (type system, memory, pointers, preprocessor)

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 ar rcs libname.a obj1.o obj2.o
Create shared library gcc -shared -o libname.so obj1.o obj2.o
Install library make install or cmake --install
Generate docs doxygen Doxyfile
Run tests make test or ctest
Check ABI abidiff lib-v1.so lib-v2.so
Package config pkg-config --cflags --libs mylib

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:

  • libmylib.so.1.2.3 - Real file with full version
  • libmylib.so.1 - Symlink (SONAME) for ABI compatibility
  • libmylib.so - Symlink for linker

macOS:

  • libmylib.1.2.3.dylib - Real file
  • libmylib.1.dylib - Symlink
  • libmylib.dylib - Symlink

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