Связывание файлов/заголовков в C
Скажем, у меня есть следующая программа (hello.c
):
#include <stdio.h>
#include <math.h>
#define NAME "ashoka"
int main(int argc, char *argv[])
{
printf("Hello, world! My name is %s\n", NAME);
}
Итак, насколько я понимаю, процесс компиляции:
-
Предварительная обработка: скопирует вставки деклараций stdio.h и math.h и заменит NAME
на "ashoka"
.
clang -E hello.c
-
Компиляция: превратит c в код сборки
clang -S hello.c
: hello.s
-
Сборка: преобразование кода сборки в код объекта
clang -c hello.s
: hello.o
-
Связывание: объединить файлы объектов в один файл, который мы будем выполнять.
clang hello.o -lm
ИЛИ (скажем, я также хочу связать hello2.o)
clang hello.o hello2.o
Итак, вот вопросы:
-
Правильно ли описан процесс?
-
На этапе связывания мы связываем файлы .o
(Object code). Я знаю, что math.h
находится в каталоге /usr/include
. Где math.o
? Как линкер находит это?
-
Что такое .a
(статические библиотеки) и .so
(динамические библиотеки) в Linux? И как они связаны с файлами .o
и этапом компоновки?
-
Скажем, я хочу поделиться библиотекой, которую я создал с миром. У меня есть файл mylib.c
, в котором я объявил и реализовал свои функции. Как бы я поделился этим, чтобы люди включили его в свои проекты, выполнив либо #include <mylib.h>
, либо #include "mylib.h"
?
Ответы
Ответ 1
- Да, хотя переход через сборку является дополнительным шагом (вы можете просто скомпилировать источник C для объекта). Внутри компилятор будет иметь гораздо больше этапов: анализ кода в AST, создание промежуточного кода (например, LLVM-бит-код для
clang
), оптимизация и т.д.
-
math.h
просто определяет протипы для стандартной математической библиотеки libm.a
(которую вы связываете с -lm
). Сами функции живут в объектных файлах, архивированных внутри libm.a
(см. Ниже).
- Статические библиотеки - это просто архивы объектных файлов. Линкером будет проверяться, какие символы используются, и извлекает и связывает файлы объектов, которые экспортируют эти символы. К этим библиотекам можно обращаться с помощью
ar
(например, ar -t
перечисляет объектные файлы в библиотеке). Динамические (или общие) библиотеки не включаются в выходной двоичный файл. Вместо этого символы, необходимые вашему коду, загружаются во время выполнения.
-
Вы просто создали бы заголовочный файл с прототипами extern
ed:
#ifndef MYLIB_H
#define MYLIB_H
extern int mylib_something(char *foo, int baz);
#endif
и отправьте его в свою библиотеку. Конечно, разработчик должен также связать (динамически) с вашей библиотекой.
Преимущество статических библиотек - надежность: сюрпризов не будет, потому что вы уже связали свой код с точной версией, с которой вы уверены, что он работает. Другие случаи, когда это может быть полезно, - это когда вы используете необычные или кратковременные библиотеки, и вы не хотите устанавливать их как общие. Это происходит за счет увеличения размера двоичных файлов.
Общие библиотеки производят меньшие двоичные файлы (потому что библиотека не находится в двоичном формате) с меньшим размером ОЗУ (поскольку ОС может загружать библиотеку один раз и делиться ею между многими процессами), но они требуют немного большего внимания, чтобы убедиться, что вы (например, см. DLL Hell в Windows).
Как отмечают @iharob, их преимущества не просто прекращаются при двоичном размере. Например, если ошибка исправлена в общей библиотеке, все программы выиграют от нее (пока она не нарушит совместимость). Кроме того, общие библиотеки обеспечивают абстрагирование между внешним интерфейсом и реализацией. Например, предположим, что ОС предоставляет библиотеку для приложений, которые могут ей взаимодействовать. С обновлениями изменяется интерфейс ОС, и реализация библиотеки отслеживает эти изменения. Если он был скомпилирован как статическая библиотека, все программы должны быть перекомпилированы с новой версией. Если бы это была общая библиотека, они бы даже не заметили ее (пока внешний интерфейс остается прежним). Другим примером являются библиотеки Linux, которые обменивают системные/дистрибутивные аспекты на общий интерфейс.
Ответ 2
Процесс, описанный выше, является правильным. Однако в подавляющем большинстве случаев код C предварительно обрабатывается и собирается в один шаг следующим образом:
clang -c hello.c
Выполнение отдельной предварительной обработки обычно выполняется только для отладки. Преобразование в сборку почти никогда не выполняется, если вы намереваетесь выполнить некоторую оптимизацию уровня ручной сборки, которая редко необходима.
Что касается ссылки, параметр -l
указывает компоновщику искать общую библиотеку формы "lib {name}.so". В вашем примере -lm
сообщает компоновщику ссылку на libm.so. По умолчанию он будет выглядеть в /usr/lib, однако вы можете использовать параметр -l
, чтобы предоставить ему список каталогов для поиска библиотек.
Вы используете флаг -B
для переключения между связыванием со статическими библиотеками или динамическими библиотеками:
clang hello.o -lm -Bstatic -lstaticlib -B dynamic -ldynamiclib
Это будет ссылка на libm.so, libstaticlib.a и libdynamiclib.so
Статические библиотеки напрямую связаны с вашим исполняемым файлом, например, файлы .o. Напротив, динамические библиотеки хранятся отдельно от исполняемого файла и загружаются во время выполнения.
Ответ 3
- Да, это процесс в целом.
- Нет файла math.o, переключатель
-lm
переключается на libm.so(общий объект, а следовательно:.so), где все символы, требуемые математическими функциями, объявленными в math.h, определены.
-
Давайте ответим на это в двух разделах
Статические библиотеки
- простые коллекции объектных файлов, сохраненные в формате архива.
Общие библиотеки
находятся (в linux) файлы ELF с символами, определенными так, как они определены в исполняемых файлах, вы связываете программы, чтобы иметь возможность использовать эти символы во время выполнения, и есть загрузчик, который загружает такие символы в которая будет использоваться.
Это практически то же самое на других платформах, таких как .dlls на окнах, они в основном скомпилированные программы, у которых отсутствует функция main(), поэтому они не могут быть выполнены напрямую. Thye содержит исполняемый код для загрузки во время выполнения. Вы можете сделать это сами, используя dlopen(3)
в linux.
Примечание: в коде, который вы отправили, некоторые вещи не произойдут, потому что вы ничего не использовали из math.h, поэтому ссылка на libm.so полностью не используется. Компиляторы также пытаются оптимизировать сгенерированный код, и в вашем случае программа эквивалентна простейшему Hello World в c. Но остальная часть вопроса верна, и имеет смысл ответить.