Как сделать так, чтобы функция имела внутреннюю библиотечную связь?

Например, если у меня есть два файла foo.c а также bar.o, а также foo.c содержит функцию foo() который ссылается на функцию bar() в bar.o:

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

Как я могу скомпилировать статическую или динамическую библиотеку, которая предоставляет foo(), но не выставляет bar()? Другими словами, я хочу bar() быть связанным только внутри библиотеки.

2 ответа

Решение

В стандартном C вы можете только экспортировать функцию или нет, опция "экспортировать только в эти файлы" отсутствует. Так что в основном вам придется двигаться bar() в foo.c и объявить это как static, Если вы хотите сохранить файл отдельно, уродливый хак будет #include это из foo.c (а не компилировать bar.o)...

С инструментами, выходящими за рамки стандарта C, вы можете удалить публичный экспорт из библиотеки по ссылке или после нее. Некоторые решения компоновщика показаны ниже, и с помощью GCC и clang (в тех случаях, когда вы можете изменить код) вы можете скрыть функцию, добавив к ней префикс нестандартного атрибута: __attribute__ ((visibility ("hidden"))) - эквивалентом этого будет единица компиляции -fvisibility=hidden при компиляции, например, bar.c,

С обходной путь

Если вы можете редактировать код C свободно, обходной путь в стандартном C будет bar()static в bar.c и доставить указатель на функцию для использования в foo() с помощью некоторых средств, например, экспортировать указатель на struct содержащий указатель на функцию (и любые другие "частные" данные), и не раскрывать детали struct вне частного заголовка, используемого только вашей библиотекой.

Например:

В bar.h (конфиденциально, не делитесь с пользователем библиотеки):

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

В 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;

В foo.c:

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

В этом решении будет экспортированный указатель с именем bar_functions, но никакие детали о данных / функциях, на которые указывают, не раскрываются через этот экспорт. Без доступа к bar.h пользователь библиотеки должен будет перепроектировать содержимое, чтобы правильно вызывать "частные" функции. В случае нескольких "приватных" функций этот подход также может объединять их в один экспортированный указатель, удаляя помехи из списка экспорта.

Линкерные решения

Исследуя конкретные линкеры, я нашел способ исключить определенные символы из динамической библиотеки:

С GNU ldсоздайте скрипт версии, такой как libfoobar.version:

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

Вызов через gcc:

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

С clangld (в OS X) создайте список неэкспортированных символов, таких как unexported (один символ на строку):

_bar

Вызов через clang:

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

В обоих случаях функция bar скрыт и внешне не вызывается, но foo остается в рабочем состоянии (и звонки bar внутренне), хотя оба имели одинаковую внешнюю видимость в своих исходных (и объектных) файлах.

Тестовый код, 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 (ссылка на библиотеку перед удалением экспорта для 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;
}

Прецедент:

# 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

Функции могут иметь только внутреннюю или внешнюю связь.

Если вы хотите использовать разные модули, ваши функции должны иметь внешнюю связь, чтобы их можно было вызывать из одного блока перевода в другой.

Вы можете использовать внешний указатель на статическую функцию, но, конечно, это позволит другим модулям вызывать функцию через указатель на функцию.

Другие вопросы по тегам