Обязательно ли ссылаться во всех зависимостях? - PullRequest
0 голосов
/ 07 января 2019

Допустим, у нас есть библиотека libutils, которая реализует две функции: function1 и function2. И скажем, функция2 использует библиотеку libanother (поэтому libutils зависит от libanother).

Теперь я хочу создать свое приложение, которое использует только function1:

g++ myapplication.cpp -o my application -lutils

Требуется ли включать libanother в связь:

g++ myapplication.cpp -o my application -lutils -lanother

Есть ли какие-либо требования к компоновщику относительно этого? Зависит ли ответ от того, являются ли библиотеки libutils и libanother общими или статическими?

Ответы [ 2 ]

0 голосов
/ 07 января 2019

Некоторые первые принципы

Для привязки программы можно ввести три вида файлов:

  • объектные файлы
  • статические библиотеки
  • общие библиотеки

Но только два таких файлов могут быть связаны в программе: объектные файлы и общие библиотеки. 1

Если вы введете любой объект файл p.o в связывание программы компоновщик связывает его в программу безоговорочно .

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

Если вы введете общую библиотеку liby.so, история разветвляется: -

  • Если опция компоновщика (ld) --as-needed действует при достижении liby.so, тогда liby.so будет связан с программой, если она необходима в том же смысле, что и элемент архива libx.a(q.o).

  • Если --as-needed имеет значение , а не , тогда liby.so безусловно связано, как объектный файл.

Если вы вызываете компоновщик через один из интерфейсов GCC вашего дистрибутива (gcc, g++, gfortran ...) в обычном режиме Кстати, тогда вы всегда получите поведение --as-needed для разделяемых библиотек по умолчанию или всегда не получите его, пока ваш Набор инструментов GCC остается тем же, потому что сборка GCC вашего дистрибутива настроит --as-needed как вариант связывания по умолчанию, или нет, в соответствии с оценкой этого дистрибутива плюсов и минусов этого. В последние годы дистрибутивы Debian (Ubuntu et al.) По умолчанию установили --as-needed. В прошлый раз я смотрел, Производные от Redhat (Fedora et al.) Этого не сделали. 2

Когда какой-либо файл file.{o|so} связан с программой, он вводит 0 или более неразрешенных ссылок в программу. Эти неразрешенные ссылки ссылки на символы в file.{o|so}, которые не определены в file.{o|so} или в любом другом файле, уже связанном с программой.

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

вплоть до вашего дела

Когда вы говорите:

мое приложение ... использует только функцию1

Вы имеете в виду, что какой-то файл будет связан с ссылками function1. Это просто предположить, что этот файл является неким объектным файлом, который будет безоговорочно связан, скажем тот, который определяет main, например

main.c

#include <stdio.h>

extern void function1(void);

void call_function1(void) {
    function1();
}

int main(void)
{
    puts("Hello World!");
}

который мы компилируем в объектный файл:

$ gcc -Wall -c main.c

Обратите внимание, что main.o ссылается на function1 несмотря на то, что мы не можем запустить программу с этой функцией main любым способом, который вызовет function1. main.o определяет функцию, call_function1, что вызывает function1; поэтому ссылка function1 указана в main.o. call_function1 это не ссылается на себя, но function1 есть. Если мы посмотрим на таблица символов main.o:

$ nm main.o
0000000000000000 T call_function1
                 U function1
                 U _GLOBAL_OFFSET_TABLE_
000000000000000c T main
                 U puts

обозначенные символы T определены. На аннотированные U имеются ссылки но не определено. Поэтому, когда мы пытаемся связать main.o в программу:

$ gcc -o prog main.o
/usr/bin/ld: main.o: in function `call_function1':
main.c:(.text+0x5): undefined reference to `function1'
collect2: error: ld returned 1 exit status

нам нужно ввести файл, который определяет function1, даже если программа связана с этим main.o невозможно назвать function1.

Ваш function1 определен каким-то образом в библиотеке libutils, которая также определяет function2. Ваша программа не ссылается на function2, но определение function2 ссылается на один или несколько символов, скажем, function3, которые определены,как-то внутри другой библиотеки libanother.

Вам нужно добавить libanother, а также libutils к связи вашего Программа

Исходя из первых принципов, мы знаем, что вы должны добавить libanother к вашей ссылке, если вы ссылаетесь на файл, который ссылается на function3. function3 упоминается в определении function2. Таким образом, вы должны связаться с libanother если вы связываете файл, который содержит определение function2.

Вам нужно связать такой файл?

Это зависит от реализации самого libutils.

Дело 1

Предположим, вы реализовали libutils как статическую библиотеку, в которой у вас есть заархивировал объектный файл, который определяет function1, и другой объектный файл, который определяет function2. Вот так:

function1.c

#include <stdio.h>

void function1(void)
{
    puts(__func__);
}

function2.c

extern void function3();

void function2(void)
{
    function3();
}

Скомпилируйте исходные файлы:

$ gcc -Wall -c function1.c function2.c

Архивирование объектных файлов:

$ ar rcs libutils.a function1.o function2.o

Ссылка prog нравится:

$ gcc -o prog main.o libutils.a

Нет проблем. Run:

$ ./prog
Hello World!

Давайте сделаем связь снова, на этот раз с диагностикой, чтобы показать, что Объектные файлы и общие библиотеки были фактически связаны:

$ gcc -o prog main.o libutils.a -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/8/crtbeginS.o
main.o
(libutils.a)function1.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6 (//lib/x86_64-linux-gnu/libc.so.6)
(//usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 (//lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 (//lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/8/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crtn.o

Большинство этих входных данных представляют собой типовые объектные файлы и общие библиотеки, которые gcc добавляет к программе связь по умолчанию. Файлы, которые мы построили, просто:

main.o
(libutils.a)function1.o

Член архива libutils.a(function2.o) вообще не был связан, потому что ни один файл не был связан с ссылками function2. Так что не важно, что libutils.a(function2.o) делает неопределенную ссылку на function3. libutils.a(function2.o) может также не существовать. Связь:

$ gcc -o prog main.o libutils.a

точно такая же связь, как:

gcc -o prog main.o function1.o

Дело 2

Предположим, вы реализовали libutils как статическую библиотеку, которая содержит один объектный файл, который определяет function1 и function2:

function12.c

#include <stdio.h>

void function1(void)
{
    puts(__func__);
}

extern void function3(void);

void function2(void)
{
    function3();
}

Скомпилируйте этот файл:

$ gcc -Wall -c function12.c

Воссоздать libutils.a:

$ rm libutils.a
$ ar rcs libutils.a function12.o

Relink prog:

$ gcc -o prog main.o libutils.a -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/8/crtbeginS.o
main.o
(libutils.a)function12.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6 (//lib/x86_64-linux-gnu/libc.so.6)
(//usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 (//lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 (//lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/8/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crtn.o
/usr/bin/ld: libutils.a(function12.o): in function `function2':
function12.c:(.text+0x32): undefined reference to `function3'
/usr/bin/ld: link errors found, deleting executable `prog'
collect2: error: ld returned 1 exit status

На этот раз, как показывает вывод -trace, наши объектные файлы:

main.o
(libutils.a)function12.o

были связаны. main.o ссылка function1, поэтому компоновщик искал libutils.a для объектного файла, который определяет function1. Найдено function12.o и связано это, включая определение function2, а также определение function1. Определение function2 ввело неразрешенную ссылку на function3, в программу. Ни один файл не был связан с этой ссылкой, поэтому сбой связи:

function12.c:(.text+0x32): undefined reference to `function3'

Неважно, что function2 не может быть вызвано программой - просто не имеет значения, что function1 нельзя назвать. 3

Дело 3

Вы реализовали libutils как разделяемую библиотеку, связав независимую от позиции связь объектные файлы function1.o и function2.o, скомпилированные из function1.c и function2.c соответственно:

$ gcc -Wall -fPIC -c function1.c function2.c
$ gcc -shared -o libutils.so function1.o function2.o

Обратите внимание, что хотя общая библиотека libutils.so представляет собой файл ELF linkage (не ar архив объектных файлов), мы можем успешно связать его несмотря на то, что function2.o содержит неопределенную ссылку на function3.

Можно связать общую библиотеку с неопределенными ссылками. Смотрите таблицу символов:

$ nm libutils.so
0000000000004030 b completed.7930
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000001060 t deregister_tm_clones
00000000000010d0 t __do_global_dtors_aux
0000000000003e18 t __do_global_dtors_aux_fini_array_entry
0000000000004028 d __dso_handle
0000000000003e20 d _DYNAMIC
0000000000001150 t _fini
0000000000001110 t frame_dummy
0000000000003e10 t __frame_dummy_init_array_entry
00000000000020f0 r __FRAME_END__
0000000000002020 r __func__.2361
0000000000001115 T function1
0000000000001142 T function2
                 U function3
0000000000004000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
000000000000202c r __GNU_EH_FRAME_HDR
0000000000001000 t _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U printf@@GLIBC_2.2.5
0000000000001090 t register_tm_clones

Есть U function3. Но вы не можете связать программу с неопределенными ссылками:

$ gcc -o prog main.o libutils.so -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/8/crtbeginS.o
main.o
libutils.so
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6 (//lib/x86_64-linux-gnu/libc.so.6)
(//usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 (//lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 (//lib/x86_64-linux-gnu/ld-linux-x86-64.so.2)
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/8/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/8/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu/crtn.o
/usr/bin/ld: libutils.so: undefined reference to `function3'
/usr/bin/ld: link errors found, deleting executable `prog'
collect2: error: ld returned 1 exit status

Когда мы ссылаемся:

main.o
libutils.so

связь не работает так же, как с:

main.o
(libutils.a)function12.o

или действительно:

$ gcc -o prog main.o function1.o function2.o
/usr/bin/ld: function2.o: in function `function2':
function2.c:(.text+0x5): undefined reference to `function3'
collect2: error: ld returned 1 exit status

Итак:

Требуется ли связывание во всех зависимостях?

Да, когда вы связываете программу . Но поймите, что зависимости от программа:

Зависимости программы: объектные файлы и / или общие библиотеки которые определяют символы, на которые ссылаются в объектных файлах и общих библиотеках которые на самом деле связаны.

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


[1] То, что компоновщик делает , чтобы связать объектный файл с программой, принципиально отличается от того, что он делает, чтобы связать общую библиотеку.

Грубо говоря, чтобы связать объектный файл, он физически объединяет части, из которых объект Файл состоит из соответствующих частей программы вывода.

Грубо говоря, чтобы связать разделяемую библиотеку liby.so, компоновщик просто записывает краткую информацию в стандартный формат в программе, который будет проанализирован загрузчиком времени выполнения, поручив ему найти и загрузить liby.so и объединить части, из которых он состоит составлено в процессе выполненной программы.

[2] Вы можете проверить, имеет ли ваша цепочка инструментов GCC значение по умолчанию --as-needed или нет путем поиска этой опции в подробном выводе связи GCC по умолчанию, например

$ gcc -v -o prog main.o function1.o 2>&1 | grep '\--as-needed'

[3] Мы можем скомпилировать объектные файлы с опцией компиляции не по умолчанию -ffunction-sections и связать их в программах с параметром компоновщика не по умолчанию -gc-sections, с эффект, что компоновщик сможет отбросить определения функций, которые не может вызвать, так что они не могут привести к каким-либо ссылкам, которые навязывают бесполезные зависимости от программы.

0 голосов
/ 07 января 2019

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

...