Связывание файлов/заголовков в 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. Но остальная часть вопроса верна, и имеет смысл ответить.