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.
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.
| name | lang-c-library-dev |
|---|---|
| description | 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. |
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 versionlibmylib.so.1- Symlink (SONAME) for ABI compatibilitylibmylib.so- Symlink for linker
macOS:
libmylib.1.2.3.dylib- Real filelibmylib.1.dylib- Symlinklibmylib.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
lang-c-dev- Foundational C programminglang-c-memory-eng- Advanced memory patterns- CMake Documentation
- Meson Build System
- Doxygen Manual
- Unity Test Framework
- Check Framework
- Criterion
- pkg-config Guide