Когда и почему компоновщик C исключает неиспользуемые символы? - PullRequest
5 голосов
/ 13 марта 2019

Я выполняю некоторые тесты с gcc, чтобы понять правило (правила), по которым оно разумно исключает неиспользуемые символы.

// main.c

#include <stdio.h>

void foo()
{
}

int main( int argc, char* argv[] )
{
  return 0;
}

.

// bar.c

int bar()
{
  return 42;
}

.

> gcc --version
gcc (GCC) 8.2.1 20181215 (Red Hat 8.2.1-6)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>
> gcc -c bar.c
> gcc -g main.c bar.o 
> nm a.out | grep "foo\|bar"
000000000040111f T bar
0000000000401106 T foo

Выше я скомпилировал bar.o и связал его с a.out при компиляции main.c.
Перечисление символов a.out показывает, что обе неиспользуемые функции - foo() и bar() - включены в исполняемый файл.

> ar -r libbar.a bar.o
ar: creating libbar.a
> gcc -g main.c -L ./ -lbar
> nm a.out | grep "foo\|bar"
0000000000401106 T foo

Выше я заархивировал bar.o в libbar.a и воссоздал a.out, на этот раз связав с libbar.a вместо bar.o. На этот раз неиспользуемая функция foo() все еще присутствует, но bar() нет.

Из этого эксперимента я могу предположить следующие "правила":

  1. Символы, связанные с объектными файлами, всегда присутствуют в исполняемых файлах. (Возможно, это объясняет, почему foo() присутствует всегда: существует ли временный / анонимный main.o, который был создан? Если это так, он будет включать foo())
  2. Если исполняемый файл связан с библиотекой , gcc будет разумно определять ненужные символы для исключения.

Выше приведены мои гипотезы, основанные на этом эксперименте - но насколько это правильно? Если кто-то разбирается в тонкостях работы ссылок, я был бы благодарен за некоторую справочную информацию, объясняющую, почему и почему происходит то, что происходит.

1 Ответ

8 голосов
/ 13 марта 2019

С оговоркой в ​​основном верно, что связывание статической библиотеки на самом деле не имеет гранулярности для каждого символа. Он имеет гранулярность для каждого члена объекта.

Пример:

Если статическая библиотека содержит файлы:

a.o 
    foo
    bar
b.o 
    baz

и необходимо определить неопределенную ссылку на foo, будет введено a.o, а вместе с ним и символ bar.

Вы можете получить эффект гранулярности на символ, когда вы компилируете с -ffunction-sections -fdata-sections, а затем связываетесь с -Wl,--gc-sections (gc обозначает сборщик мусора), но имейте в виду, что опции компилятора / компоновщика - gcc / clang-специфичные и что они имеют незначительную производительность / размер кода.

-ffunction-sections помещает каждую функцию в отдельный раздел (что-то вроде своего собственного объектного файла), а -fdata-sections делает то же самое для видимых извне глобальных переменных. -Wl,--gc-sections затем запускает сборщик мусора после того, как объектные файлы связаны как обычно, и сборщик мусора удаляет все разделы (=> символы), которые недоступны.

(-ffunction-sections также полезно, если вы хотите, чтобы size -A the_objectfile.o давал вам размеры функций, и если вы также хотите, чтобы размеры этих функций незначительно колеблется в зависимости от положения функций (из-за требований выравнивания). *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...