#c #linker #c-preprocessor
#c #компилятор-конструкция #компоновщик #препроцессор #заголовочные файлы
Вопрос:
Хорошо, до сегодняшнего утра я был полностью сбит с толку этими терминами. Надеюсь, я уловил разницу.
Во-первых, путаница заключалась в том, что, поскольку препроцессор уже включает файлы заголовков в код, содержащий функции, какие библиотечные функции компоновщик связывает с объектным файлом, созданным ассемблером / компилятором? Часть путаницы в первую очередь возникла из-за моего незнания разницы между файлом заголовка и библиотекой.
После небольшого поиска в Google и переполнения стека (это термин? :p), я понял, что файл заголовка в основном содержит объявления функций, тогда как фактическая реализация находится в другом двоичном файле, называемом библиотекой (я все еще не уверен в этом на 100%).
Итак, предположим, что в следующей программе:-
#include<stdio.h>
int main()
{
printf("whatever");
return 0;
}
Препроцессор включает содержимое файла заголовка в код. Компилятор / compiler assembler выполняет свою работу, а затем, наконец, компоновщик объединяет этот объектный файл с другим объектным файлом, который фактически сохранил способ printf()
работы.
Правильно ли я понимаю? Я могу быть способом off…so не могли бы вы мне помочь, пожалуйста?
Редактировать: я всегда задавался вопросом о C STL. Меня всегда смущало, что это такое, набор всех этих заголовков или что? Теперь, прочитав ответы, могу ли я сказать, что STL — это объектный файл / что-то, что напоминает объектный файл?
А также я подумал, где я мог бы прочитать определения функций, таких как pow()
, sqrt()
и т.д. И т.п. Я бы открыл файлы заголовков и ничего не нашел. Итак, определение функции в библиотеке представлено в двоичной нечитаемой форме?
Комментарии:
1. Понимание, о котором вы упоминаете в коде, является правильным.
2. и, наконец, создается исполняемый файл или другая библиотека …. 😉
3. Одно объяснение (специфичное для C, но C практически то же самое) можно найти в моем руководстве по C, в главе о заголовках: masters-of-the-void.com/book10.htm
Ответ №1:
Исходный файл C проходит через два основных этапа: (1) этап препроцессора, на котором исходный код C обрабатывается утилитой препроцессора, которая ищет директивы препроцессора и выполняет эти действия, и (2) этап компиляции, на котором обработанный исходный код C затем фактически компилируется для создания файлов объектного кода.
Препроцессор — это утилита, которая выполняет обработку текста. Он принимает в качестве входных данных файл, содержащий текст (обычно исходный код C), который может содержать директивы препроцессора, и выводит измененную версию файла, применяя любые найденные директивы к текстовому вводу для генерации текстового вывода.
Файл не обязательно должен быть исходным кодом C, потому что препроцессор выполняет обработку текста. Я видел, что препроцессор C используется для расширения make
утилиты, позволяя включать директивы препроцессора в файл make. Файл make с директивами препроцессора C запускается через утилиту препроцессора C, и результирующий вывод затем подается в make
для выполнения фактической сборки целевого объекта make.
Библиотеки и компоновка
Библиотека — это файл, который содержит объектный код различных функций. Это способ упаковки выходных данных из нескольких исходных файлов, когда они скомпилированы в один файл. Часто файл библиотеки предоставляется вместе с файлом заголовка (включаемым файлом), обычно с расширением файла .h . Файл заголовка содержит объявления функций, объявления глобальных переменных, а также директивы препроцессора, необходимые для библиотеки. Итак, чтобы использовать библиотеку, вы включаете файл заголовка, предоставленный с помощью #include
директивы, и связываетесь с файлом библиотеки.
Приятной особенностью файла библиотеки является то, что вы предоставляете скомпилированную версию вашего исходного кода, а не сам исходный код. С другой стороны, поскольку файл библиотеки содержит скомпилированный исходный код, компилятор, используемый для создания файла библиотеки, должен быть совместим с компилятором, используемым для компиляции ваших собственных файлов исходного кода.
Обычно используются два типа библиотек. Первый и более старый тип — это статическая библиотека. Вторая и более новая — это динамическая библиотека (библиотека динамических ссылок или DLL в Windows и разделяемая библиотека или ОКОЛО ТОГО в Linux). Разница между ними заключается в том, что функции в библиотеке привязаны к исполняемому файлу, который использует файл библиотеки.
Компоновщик — это утилита, которая использует различные объектные файлы и файлы библиотеки для создания исполняемого файла. Когда в исходном файле C используется внешняя или глобальная функция или переменная, используется своего рода маркер, сообщающий компоновщику, что в этот момент необходимо вставить адрес функции или переменной.
Компилятор C знает только то, что находится в исходном коде, который он компилирует, и не знает, что находится в других файлах, таких как объектные файлы или библиотеки. Итак, задача компоновщика состоит в том, чтобы использовать различные объектные файлы и библиотеки и создавать окончательные соединения между частями, заменяя маркеры фактическими соединениями. Итак, компоновщик — это утилита, которая «связывает» различные компоненты, заменяя маркер глобальной функции или переменной в объектных файлах и библиотеках ссылкой на фактический объектный код, который был сгенерирован для этой глобальной функции или переменной.
На этапе компоновки разница между статической библиотекой и динамической или разделяемой библиотекой становится очевидной. Когда используется статическая библиотека, фактический объектный код библиотеки включается в исполняемый файл приложения. Когда используется динамическая или разделяемая библиотека, объектный код, включенный в исполняемый файл приложения, является кодом для поиска разделяемой библиотеки и подключения к ней при запуске приложения.
В некоторых случаях одно и то же имя глобальной функции может использоваться в нескольких разных объектных файлах или библиотеках, поэтому компоновщик обычно просто использует первый, с которым сталкивается, и выдает предупреждение о других найденных.
Краткое описание компиляции и ссылки
Итак, основной процесс для компиляции и компоновки программы на C — это:
-
утилита препроцессора генерирует исходный код C для компиляции
-
компилятор компилирует исходный код C в объектный код, генерирующий набор объектных файлов
-
компоновщик связывает различные объектные файлы вместе с любыми библиотеками в исполняемый файл
Приведенный выше базовый процесс, однако при использовании динамических библиотек он может усложниться, особенно если часть создаваемого приложения имеет динамические библиотеки, которые оно генерирует.
Загрузчик
Существует также этап, когда приложение фактически загружается в память и запускается выполнение. Операционная система предоставляет утилиту, загрузчик, которая считывает исполняемый файл приложения и загружает его в память, а затем запускает приложение. Начальная точка или точка входа для исполняемого файла указана в исполняемом файле, поэтому после того, как загрузчик считает исполняемый файл в память, он запустит исполняемый файл, перейдя к адресу точки входа в память.
Одна из проблем, с которой может столкнуться компоновщик, заключается в том, что иногда он может столкнуться с маркером при обработке файлов объектного кода, для которых требуется фактический адрес памяти. Однако компоновщик не знает фактического адреса памяти, потому что адрес будет меняться в зависимости от того, где в памяти загружено приложение. Таким образом, компоновщик помечает это как что-то, что утилита загрузчика должна исправить, когда загрузчик загружает исполняемый файл в память и готовится запустить его.
С современными процессорами с аппаратно поддерживаемым отображением виртуального адреса в физический адрес или трансляцией эта проблема фактического адреса памяти редко является проблемой. Каждое приложение загружается по одному и тому же виртуальному адресу, а преобразование аппаратного адреса имеет дело с фактическим физическим адресом. Однако более старые процессоры или более дешевые процессоры, такие как микроконтроллеры, которым не хватает аппаратной поддержки блока управления памятью (MMU) для преобразования адресов, по-прежнему нуждаются в решении этой проблемы.
Точки входа и среда выполнения C
Последняя тема — среда выполнения C и main()
и точка входа в исполняемый файл.
Среда выполнения C — это объектный код, предоставляемый производителем компилятора, который содержит точку входа для приложения, написанного на C. main()
Функция — это точка входа, предоставляемая программистом, пишущим приложение, однако это не та точка входа, которую видит загрузчик. main()
Функция вызывается средой выполнения C после запуска приложения, и код среды выполнения C настраивает среду для приложения.
Среда выполнения C не является стандартной библиотекой C. Цель среды выполнения C — управлять средой выполнения приложения. Цель стандартной библиотеки C — предоставить набор полезных служебных функций, чтобы программисту не приходилось создавать свои собственные.
Когда загрузчик загружает приложение и переходит к точке входа, предоставляемой средой выполнения C, среда выполнения C затем выполняет различные действия инициализации, необходимые для обеспечения надлежащей среды выполнения для приложения. Как только это будет сделано, среда выполнения C вызывает main()
функцию, чтобы код, созданный разработчиком приложения или программистом, начал выполняться. Когда main()
возвращается или когда exit()
вызывается функция, среда выполнения C выполняет любые действия, необходимые для очистки и закрытия приложения.
Комментарии:
1. Спасибо за такое подробное объяснение. Как уже упоминалось —
One problem the linker can run into is that sometimes it may come across a marker when it is processing the object code files that requires an actual memory address.
Не могли бы вы привести пример такой ситуации, когда требуется фактический адрес памяти?2. @YugSingh что-то, что приходит на ум, — это когда загружается DLL. Я думал о вашем вопросе, и мне интересно, нуждаются ли части этого ответа в обновлении с помощью технологий виртуальной памяти и MMU, распространенных даже для встроенных процессоров.
Ответ №2:
Это чрезвычайно распространенный источник путаницы. Я думаю, что самый простой способ понять, что происходит, — это взять простой пример. Забудьте на мгновение о библиотеках и подумайте о следующем:
$ cat main.c
extern int foo( void );
int main( void ) { return foo(); }
$ cat foo.c
int foo( void ) { return 0; }
$ cc -c main.c
$ cc -c foo.c
$ cc main.o foo.o
Объявление extern int foo( void )
выполняет точно ту же функцию, что и файл заголовка библиотеки. foo.o
выполняет функцию библиотеки. Если вы понимаете этот пример и почему ни cc main.c
ни cc main.o
не работают, тогда вы понимаете разницу между файлами заголовков и библиотеками.
Комментарии:
1. Спасибо.. Я не уверен, что полностью понимаю этот пример… но это потому, что я не понимаю слово «extren». Я проведу исследование по этому вопросу немного позже и снова доставлю вам сообщение в случае сомнений. 🙂
2. Похоже, что команда cat Linux используется для создания двух небольших простых файлов исходного кода C, main.c и foo.c, каждый из которых сначала компилируется, а затем связывается. Команда cc обладает достаточным интеллектом, так что, если вы укажете объектные файлы, файлы main.o и foo.o, она просто выполнит ссылку с использованием этих файлов.
3.
cat
не создает файлы, а просто отображает их.
Ответ №3:
Да, почти правильно. За исключением того, что компоновщик не связывает объектные файлы, но также и библиотеки — в этом случае стандартная библиотека C (libc) связана с вашим объектным файлом. Остальные ваши предположения, похоже, верны относительно этапов компиляции разница между заголовком и библиотекой.
Комментарии:
1. @DevSolar за исключением того, что это не так. Он создается из объектных файлов, но это, конечно, не сам объектный файл.
2. @H2CO3: разделяемые библиотеки часто считаются самостоятельными объектными файлами.
3. @H2CO3: нет, разделяемые библиотеки. Статические библиотеки — это
ar
архивы простых старых объектных файлов. Разделяемые библиотеки — это файлы «общих объектов» (.so
расширение в Linux и некоторых других системах).4. @larsmans Я в курсе этого. Но технически, несмотря на то, что они имеют название «общий объект», они не являются объектными файлами. Они правильно связаны с помощью компоновщика, точно так же, как исполняемый файл, и могут быть открыты для прямого выполнения их содержимого (например,
dlopen()
API), потому что они содержат информацию о местоположении / адресе, которой нет в объектных файлах.5. @H2CO3:
dlopen()
начинается с «dl», потому что это вызов загрузчика динамических ссылок . Ref. «.dll» в Windows (библиотека динамических ссылок )…