C static linkage: choosing the export boundary for internal helpers in a shared library
0 reputation · 01 Oct 2023, 06:07 UTC
0 reputation · 01 Oct 2023, 06:07 UTC
In C, functions and file-scope variables have external linkage by default, so any translation unit can reach them through an extern declaration. Marking helpers and private globals static gives them internal linkage and confines them to one .c file, which is the usual way to keep a module's internals from becoming accidental public API.
The boundary breaks down inside a shared library split across several .c files. Some internal helpers are needed by more than one translation unit, so they cannot be static; with default ELF visibility they are exported from the resulting .so and become reachable by any application that links against it, even though they never appear in the public header.
The open decision is where to place the visibility boundary: static linkage alone, visibility attributes such as __attribute__((visibility("hidden"))), or a linker version script that whitelists exports. Assume a C11 codebase built as an ELF shared library with GCC or Clang.
static, and which cross-file helpers genuinely require external linkage within the library?.so's public interface?static linkage offer any protection for these symbols, or is it purely a per-file boundary?31775 reputation · 01 Oct 2023, 13:18 UTC
Use static only for helpers that are truly confined to a single .c file. Any helper that must be called from more than one translation unit needs external linkage, but you must hide it from the shared library’s public interface.
static gives internal linkage; the symbol is not placed in the object’s global symbol table and therefore cannot be referenced from another translation unit.-fvisibility=default), any non‑static function or variable appears in the dynamic symbol table (.dynsym) and is callable by downstream code.__attribute__((visibility(\"hidden\")))) or a linker version script that whitelists exports remove the symbol from the dynamic symbol table while preserving ordinary external linkage inside the library.static does not protect a symbol from being referenced by other translation units; it merely prevents the symbol from having external linkage. If another file declares it extern the linker will still resolve it, but the symbol will not be exported from the .so.static (or better, place them in an unnamed namespace if using C++).static in a header that is included where needed./* helper.c */
#include \"helper.h\"
void __attribute__((visibility(\"hidden\"))) internal_helper(void) {
/* implementation */
}
-fvisibility=hidden and create a linker version script that lists only the public API symbols to be exported (default visibility). Example script:{
global:
public_func1;
public_func2;
local:
*;
};Then link with -Wl,--version-script=mysym.map.readelf -s libfoo.so | grep internal_helper – the symbol should appear in the symbol table but not in the dynamic section (readelf -d libfoo.so or nm -D libfoo.so should not list it).If you already build with -fvisibility=default and have no version script, confirm whether you can add a linker script without breaking existing consumers; this determines whether the attribute‑per‑function approach or a centralized export map is preferable.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.