связывание со статической библиотекой в ​​C - PullRequest
0 голосов
/ 05 ноября 2018

Привет, я новичок в C и Linking, я читал книгу, в которой есть вопрос по связыванию со статической библиотекой:

Пусть a и b обозначают объектные модули или статические библиотеки в текущем каталоге, и пусть a → b обозначает, что a зависит от b, в том смысле, что b определяет символ, который ссылается на. Для каждого из следующих сценариев покажите минимальную команду строка (т.е. строка с наименьшим количеством аргументов объектного файла и библиотеки), которая будет разрешить статическому компоновщику разрешить все ссылки на символы:

p.o → libx.a → liby.a and liby.a → libx.a →p.o

и ответ, данный книгой:

gcc p.o libx.a liby.a libx.a

Я запутался, не должен ли быть ответ:

gcc p.o libx.a liby.a libx.a p.o

в противном случае, как неопределенный символ в libx.a разрешен p.o?

1 Ответ

0 голосов
/ 05 ноября 2018

В случае, если ваш учебник 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.
...