Search code examples
cgcccompilationlinkerld

How can I make a function have library-internal linkage?


For example, if I have two files foo.c and bar.o, and foo.c contains a function foo() that references a function bar() in bar.o:

int foo(int x) { x = bar(x); /* ... */ }

How can I compile a static or dynamic library that exposes foo(), but does not expose bar()? In other words, I want bar() to be linked only inside the library.


Solution

  • With standard C you can only either export the function or not, there is no “export only to these files” option. So basically you would have to move bar() to foo.c and declare it as static. If you wish to keep the file separate, an ugly hack would be to #include it from foo.c (and not compile bar.o)…

    With tools outside the scope of standard C you can remove the public export from the library at or after linking. Some linker solutions are shown below, and with GCC and clang (in cases where you can modify the code) you can hide a function by prefixing it with the non-standard attribute: __attribute__ ((visibility ("hidden"))) – the compilation-unit-wide equivalent of this would be the option -fvisibility=hidden when compiling, e.g., bar.c.

    C workaround

    If you can edit the C code freely, a workaround in standard C would be to make bar() static within bar.c and deliver a function pointer to it for use in foo() through some means, e.g., export a pointer to a struct containing the function pointer (and any other “private” data), and do not expose the details of the struct outside of a private header used only by your library.

    For example:

    In bar.h (private, do not share with the library's user):

    struct bar_private_struct { int (*bar)(int); };
    extern struct bar_private_struct *bar_functions;
    

    In bar.c:

    #include "bar.h"
    static int bar (int x) { /* … */ return x; }
    static struct bar_private_struct functions = { bar };
    struct bar_private_struct *bar_functions = &functions;
    

    In foo.c:

    #include "bar.h"
    int foo (int x) { x = bar_functions->bar(x); /* … */ }
    

    In this solution there will be an exported pointer called bar_functions, but no details about the data/functions pointed to are revealed through this export. Without access to bar.h the user of the library would have to reverse engineer the contents to call the “private” functions correctly. In case of multiple “private” functions this approach can also condense them to a single exported pointer, removing clutter from the list of exports.

    Linker solutions

    Exploring specific linkers, I found a way to exclude specific symbols from a dynamic library:

    With GNU ld, create a version script, such as libfoobar.version:

    FOOBAR {
        global: *;
        local: bar;
    };
    

    Invocation through gcc:

    gcc -shared -o libfoobar.so foo.o bar.o -Wl,-version-script=libfoobar.version
    

    With the clang ld (on OS X) create a list of unexported symbols, such as unexported (one symbol per line):

    _bar
    

    Invocation through clang:

    clang -shared -o libfoobar.dylib foo.o bar.o -Wl,-unexported_symbols_list,unexported
    

    In both cases the function bar is hidden and externally uncallable but foo remains operational (and calls bar internally), even though both had the same external visibility in their respective source (and object) files.

    Test code, foo.c:

    int bar(int x);
    int foo (int x) { return bar(x) * 3; }
    

    bar.c:

    int bar (int x) { return x * 2; }
    

    main.c (link against the library before removing the export for bar):

    #include <stdio.h>
    int foo(int x);
    int bar(int x);
    int main () {
        (void) printf("foo(2) = %d\n", foo(2));
        (void) printf("bar(2) = %d\n", bar(2));
        return 0;
    }
    

    Test case:

    # before unexporting bar:
    $ nm -gU libfoobar.dylib
    0000000000000f70 T _bar
    0000000000000f50 T _foo
    $ ./main
    foo(2) = 12
    bar(2) = 4
    
    # after unexporting bar:
    $ nm -gU libfoobar.dylib
    0000000000000f50 T _foo
    $ ./main
    foo(2) = 12
    dyld: lazy symbol binding failed: Symbol not found: _bar