В случае, если ваш учебник C не дает понять, связь
поведение, которое автор пытается проиллюстрировать этим
упражнение не является обязательным для Стандарта C и фактически является поведением
GNU binutils
linker ld
- системный компоновщик по умолчанию в Linux,
обычно вызывается от вашего имени gcc|g++|gfortran
и т. д. - и, возможно,
но не обязательно поведение других компоновщиков, с которыми вы можете столкнуться.
Если вы дали нам упражнение точно, автором может быть кто-то, кто не понимает статического
ссылки так же хорошо, как было бы лучше для написания учебников об этом, или, возможно, просто не
выражать себя с большой осторожностью.
Если мы не связываем программу , компоновщик по умолчанию не будет
даже настаивать на разрешении всех ссылок на символы. Итак, по-видимому, мы
связывание программы (не общей библиотеки) и, если ответ:
gcc p.o libx.a liby.a libx.a
на самом деле то, что говорится в учебнике, тогда программа должна быть такой.
Но программа должна иметь функцию main
. Где находится функция main
и каковы его связи с p.o
, libx.a
и liby.a
? это
имеет значение, и нам не сказали.
Итак, давайте предположим, что p
обозначает программу , и что основная функция находится в
наименее определено в p.o
. Странно, хотя liby.a
зависело бы
на p.o
, где p.o
- основной объектный модуль программы, он будет
странность для функции main
, которая должна быть определена в члене статической библиотеки.
Предполагая, что так много, вот несколько исходных файлов:
p.c
#include <stdio.h>
extern void x(void);
void p(void)
{
puts(__func__);
}
int main(void)
{
x();
return 0;
}
x.c
#include <stdio.h>
void x(void)
{
puts(__func__);
}
y.c
#include <stdio.h>
void y(void)
{
puts(__func__);
}
callx.c
extern void x(void);
void callx(void)
{
x();
}
cally.c
extern void y(void);
void cally(void)
{
y();
}
callp.c
extern void p(void);
void callp(void)
{
p();
}
Скомпилируйте их все в объектные файлы:
$ gcc -Wall -Wextra -c p.c x.c y.c callx.c cally.c callp.c
И сделать статические библиотеки libx.a
и liby.a
:
$ ar rcs libx.a x.o cally.o callp.o
$ ar rcs liby.a y.o callx.o
Теперь p.o
, libx.a
и liby.a
выполняют условия упражнения:
p.o → libx.a → liby.a and liby.a → libx.a →p.o
Потому что:
p.o
относится, но не определяет x
, что
определено в libx.a
.
libx.a
определяет cally
, что относится, но не определяет y
,
который определен в liby.a
liby.a
определяет callx
, что относится, но не определяет x
,
который определен в libx.a
.
libx.a
определяет callp
, что относится, но не определяет p
,
который определен в p.o
.
Мы можем подтвердить с nm
:
$ nm p.o
0000000000000000 r __func__.2252
U _GLOBAL_OFFSET_TABLE_
0000000000000013 T main
0000000000000000 T p
U puts
U x
p.o
определяет p
(= T p
) и ссылки x
(= U x
)
$ nm libx.a
x.o:
0000000000000000 r __func__.2250
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T x
cally.o:
0000000000000000 T cally
U _GLOBAL_OFFSET_TABLE_
U y
callp.o:
0000000000000000 T callp
U _GLOBAL_OFFSET_TABLE_
U p
libx.a
определяет x
(= T x
) и ссылки y
(= U y
) и
ссылки p
(= U p
)
$ nm liby.a
y.o:
0000000000000000 r __func__.2250
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T y
callx.o:
0000000000000000 T callx
U _GLOBAL_OFFSET_TABLE_
U x
liby.a
определяет y
(= T y
) и ссылки x
(= U x
)
Теперь связь с учебником, безусловно, удалась:
$ gcc p.o libx.a liby.a libx.a
$ ./a.out
x
Но разве это кратчайшая возможная связь? Нет. Это:
$ gcc p.o libx.a
$ ./a.out
x
Почему? Позволяет повторно запустить связь с диагностикой, чтобы показать, какой из наших объектов
файлы были на самом деле связаны:
$ gcc p.o libx.a -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
p.o
(libx.a)x.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/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
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
Они были:
p.o
(libx.a)x.o
p.o
был впервые связан с программой, потому что входной файл .o
всегда связаны, безусловно.
Затем пришло libx.a
. Читать
статические-1164 * найти библиотеки *
чтобы понять, как с этим справился компоновщик. После ссылки p.o
он имел
только одна неразрешенная ссылка - ссылка на x
. Он осмотрел libx.a
ищет
объектный файл, который определяет x
. Найдено (libx.a)x.o
. Извлечено x.o
из libx.a
и связал его, и тогда это было сделано . 1
Все отношения зависимости, включающие liby.a
: -
(libx.a)cally.o
зависит от (liby.a)y.o
(liby.a)callx.o
зависит от (libx.a)x.o
не имеют отношения к связи, потому что связь не нужна любая
объектных файлов в liby.a
.
Учитывая, что автор говорит, что правильный ответ, мы можем перепроектировать
упражнение, которое они стремились заявить. Вот оно:
Объектный модуль p.o
, который определяет main
, ссылается на символ x
, что он
не определяет, и x
определен в элементе x.o
статической библиотеки libxz.a
(libxz.a)x.o
относится к символу y
, который он не определяет, и y
определяется в элементе y.o
статической библиотеки liby.a
(liby.a)y.o
относится к символу z
, который он не определяет, и z
определяется в элементе z.o
из libxz.a
.
(liby.a)y.o
относится к символу p
, который он не определяет, а p
определяется в p.o
Что такое минимальная команда связывания, использующая p.o
, libxz.a
, liby.a
что получится?
Новые исходные файлы:
p.c
Stays as before.
x.c
#include <stdio.h>
extern void y();
void cally(void)
{
y();
}
void x(void)
{
puts(__func__);
}
y.c
#include <stdio.h>
extern void z(void);
extern void p(void);
void callz(void)
{
z();
}
void callp(void)
{
p();
}
void y(void)
{
puts(__func__);
}
z.c
#include <stdio.h>
void z(void)
{
puts(__func__);
}
Новые статические библиотеки:
$ ar rcs libxz.a x.o z.o
$ ar rcs liby.a y.o
Теперь связь:
$ gcc p.o libxz.a
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
терпит неудачу, как и:
$ gcc p.o libxz.a liby.a
liby.a(y.o): In function `callz':
y.c:(.text+0x5): undefined reference to `z'
collect2: error: ld returned 1 exit status
и
$ gcc p.o liby.a libxz.a
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
и (ваш выбор):
$ gcc p.o liby.a libxz.a p.o
p.o: In function `p':
p.c:(.text+0x0): multiple definition of `p'
p.o:p.c:(.text+0x0): first defined here
p.o: In function `main':
p.c:(.text+0x13): multiple definition of `main'
p.o:p.c:(.text+0x13): first defined here
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
завершается ошибкой с ошибками неопределенных ссылок и ошибок множественного определения.
Но из учебника ответ:
$ gcc p.o libxz.a liby.a libxz.a
$ ./a.out
x
сейчас прав.
Автор пытался описать взаимную зависимость между двумя
статические библиотеки в связке программ, но наткнулся на тот факт, что такая взаимозависимость
может существовать, только если для связи требуется хотя бы один объектный файл из каждой библиотеки , которая
относится к некоторому символу, который определен объектным файлом в библиотеке other .
Уроки, которые можно извлечь из исправленного упражнения:
Объектный файл foo.o
, который появляется во входах компоновщика, никогда не должен появляться
более одного раза, потому что он будет связан безусловно , и когда это
связано определение, которое он предоставляет для любого символа s
будет служить для разрешения
все ссылки на s
, которые накапливаются для любых других входов компоновщика. Если foo.o
Ввод дважды, вы можете получить только ошибки для множественного определения s
.
Но там, где есть взаимозависимость между статическими библиотеками в связи
можно решить, введя одну из библиотек дважды. Потому что объектный файл
извлекается из статической библиотеки и связывается тогда и только тогда, когда этот объектный файл
необходимо , чтобы определить неразрешенную ссылку на символ, которую компоновщик пытается определить
в тот момент, когда в библиотеку вводится . Итак, в исправленном примере:
p.o
является входным и безоговорочно связанным.
x
становится неразрешенной ссылкой.
libxz.a
вводится.
- Определение
x
найдено в (libxz.a)x.o
.
(libxz.a)x.o
извлечено и связано.
x
разрешено.
- Но
(libxz.a)x.o
относится к y
.
y
становится неразрешенной ссылкой.
liby.a
является вводом.
- Определение
y
встречается в (liby.a)y.o
.
(liby.a)y.o
извлечено и связано.
y
разрешено.
- Но
(liby.a)y.o
относится к z
.
z
становится неразрешенной ссылкой.
libxz.a
снова вводится .
- Определение
z
встречается в libxz.a(z.o)
libxz.a(z.o)
извлечено и связано.
z
разрешено.
[1] Как показывает вывод
-trace
, строго говоря, связь не была
сделано, пока весь шаблон, следующий за
(libx.a)x.o
, также не будет связан,
но это один и тот же шаблон для каждой связи программы C.