Когда библиотека C статически связана, добавляется ли вся библиотека в исполняемый файл? - PullRequest
0 голосов
/ 29 июня 2018

Справочная информация: я пытаюсь сравнить требования к памяти для двух частей кода, которые выполняют некоторые численные вычисления. Для этого я сравниваю размер скомпилированных C-кодов со статически связанной математической библиотекой.

Однако я нахожу некоторые странные результаты, которые, кажется, указывают, что вся библиотека добавляется. Я описываю MWE ниже

// Program ex1.c
# include<math.h>
void main (void)
{
    float a = exp(2);

}

И

// Program ex2.c
# include <math.h>
void main(void)
{
    float a = exp(2);
    float b = pow(3,4);
    float c = sin(3.14159);
}

Я компилирую файлы следующим образом:

gcc -static -o ex1static.out ex1.c -lm
gcc -static -o ex2static.out ex2.c -lm

Если скомпилированный объект для программы 1 содержал код только для exp (), а код для скомпилированного объекта для программы 2 содержал код для exp (), pow () и sin (), то второй будет больше первый. Но оба объекта имеют одинаковый размер 912,6 кБ.

Почему это происходит, и есть ли способ убедиться, что к объектам добавляются только необходимые части кода?

1 Ответ

0 голосов
/ 29 июня 2018

Статические библиотеки - это архивы объектных файлов, и при связывании в статической библиотеке добавляются только те члены объектного файла архива, которые разрешают хотя бы одну неопределенную ссылку.

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

Кроме этого, вы можете добиться аналогичного эффекта, если библиотека скомпилирована с -ffunction-sections / -fdata-sections и затем вы передадите --gc-sections компоновщику.

Подход -ffunction-sections -fdata-sections в основном эквивалентен подходу «один глобальный источник», но использование исходных файлов для установления границ является более гибким, так как иногда группирование объектов может быть желательным (большие единицы перевода могут привести к более компактным и более оптимизированный код).

В любом случае, в вашем случае (библиотека не находится под вашим контролем), все, что вы можете попробовать, это -Wl,--gc-sections (опция -Wl для префиксов gcc, которую gcc должен передать компоновщику) Благодаря вашему примеру и glibc я смог сбросить около 41 КБ с исходных 849 КБ.

Не очень впечатляет, но glibc не собирается с учетом статических связей. Вы можете получить намного лучшие результаты с библиотекой libc, такой как musl-libc .

for ex in ex{1,2}.c; do for flg in '' -Wl,--gc-sections; do echo "$ex $flg"; musl-gcc -O0 $ex -static -lm $flg call.c && \ls -l a.out ; done ; done
ex1.c 
-rwxrwx--- 1 pjmp pjmp 8064 Jun 29 19:11 a.out
ex1.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 7744 Jun 29 19:11 a.out
ex2.c 
-rwxrwx--- 1 pjmp pjmp 8064 Jun 29 19:11 a.out
ex2.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 7744 Jun 29 19:11 a.out

Теперь это лучше, но вам может быть интересно, почему одинаковые размеры, например, 1 и 2.

Если вы добавите -Wl,--print-map, вы обнаружите, что соответствующие объектные файлы из musl-libc вообще не включены в любом случае. Причина в том, что gcc знает об этих стандартных функциях и обманывает, вставляя коды операций вместо сгенерированных вызовов функций. Вы можете в некоторой степени победить обман gcc, добавив слой косвенности, поддерживаемый другим модулем перевода.

call.c:

double call1(double(*X)(double A), double A) { return X(A); }
double call2(double(*X)(double A,double B), double A, double B){ return X(A,B); }

ex1.c

# include<math.h>
double call1(double(*X)(double A), double A);
double call2(double(*X)(double A,double B), double A, double B);
int main (void)
{
    float a = call1(exp,2);
}

Ex2.c

# include <math.h>
double call1(double(*X)(double A), double A);
double call2(double(*X)(double A,double B), double A, double B);
int main(void)
{
    float a = call1(exp,(2));
    float b = call2(pow,3,4);
    float c = call1(sin,(3.14159));
}

Теперь это дает мне:

Ex1.c 
-rwxrwx--- 1 pjmp pjmp 8216 Jun 29 19:15 a.out
Ex1.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 7984 Jun 29 19:15 a.out
Ex2.c 
-rwxrwx--- 1 pjmp pjmp 17088 Jun 29 19:15 a.out
Ex2.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 16856 Jun 29 19:15 a.out

- заметное различие между двумя примерами, которое возможно благодаря тому, как мусл состоит из много небольших исходных / объектных файлов , чтобы при статической компоновке добавлялось не больше (или не намного), чем соответствующий ссылочный код.

...