Frontend Labs
⋯

40. ABI, linking, and designing a stable C API

40.1 Opaque pointers: the technique behind Section 35's CURL*

// mylib.h — the public header. Note: no struct fields visible at all.
typedef struct MyContext MyContext;

MyContext *mycontext_create(void);
void       mycontext_set_option(MyContext *ctx, int key, int value);
void       mycontext_destroy(MyContext *ctx);
// mylib.c — the private definition, invisible to anyone who only has the header
struct MyContext {
    int option_a;
    int option_b;
    void *internal_state;
};

MyContext *mycontext_create(void) { return calloc(1, sizeof(struct MyContext)); }
void mycontext_destroy(MyContext *ctx) { free(ctx); }

Because callers never see struct MyContext's layout, you can add fields, reorder them, or change internal_state's type in a later release without breaking binaries already linked against the old version — only mylib.c needs to be recompiled. This is precisely why curl, SQLite (sqlite3*), and OpenSSL (SSL_CTX*) all expose opaque pointers instead of public structs: it's the C equivalent of an interface, and it's the only realistic way to version a C library that other people link against.

40.2 Symbol visibility: don't export what isn't API

// Only symbols marked default-visibility (or not hidden) end up in the shared library's
// exported symbol table; everything else becomes a true implementation detail.
__attribute__((visibility("default")))
int mylib_public_function(void);

__attribute__((visibility("hidden")))
int mylib_internal_helper(void);   // callable within the library, invisible to linkers outside it
clang -fvisibility=hidden -shared -o libmylib.so mylib.c   # hide everything by default,
                                                             # opt individual symbols back in

Real-world example. This is why a well-built shared library's nm -D libfoo.so shows only its intended public API, not every static helper — smaller exported symbol tables load faster, can't be accidentally depended on by other code, and let the library's authors freely refactor internals (Section 40.1's whole point) without an API review.


Generated from real-life-c-guide.md · 8 Oct 2026 16:36