Директива #include приведет к размещению содержимого файла заголовка в исходном коде перед компиляцией: например, если я включил stdio.h, препроцессор будет работать над размещением всего содержимого stdio.h в исходном коде, тогда компиляция isn Не так ли?
Поэтому давайте предположим, что я просто использую функцию printf() в своем коде. Таким образом, должно произойти что-то, что произойдет после компиляции и между связыванием, которое удалит все реализации функций, которые были включены из файла заголовка, и только вставьте реализацию функции printf() в таблицу импорта исполняемого файла, зная, что все остальные функции были скомпилированы с источником. Вы можете объяснить это мне, пожалуйста?
Функция printf
фактически не находится в файле заголовка, она находится в библиотеке, которая автоматически связана с исполняемым файлом компоновщиком.
Заголовочные файлы должны содержать только прототипы функций, т.е. Объявление функций, а не определений. И поскольку в заголовочном файле нет определений функций, для них фактически не будет создан код, и компилятор будет удостовериться, что только созданные вами функции получат запись в сгенерированном объектном файле, чтобы линкер знал об этом.
Прототип функции, объявленный в файле заголовка C, действует как ограничение времени компиляции. Он определяет правильное количество параметров и правильные типы этих параметров. Проверка таких ограничений происходит во время фазы компиляции (т.е. *.c → *.o
).
Статическая фаза связывания исключает неиспользуемые двоичные объекты из библиотек программирования для каждого объекта. Библиотека программирования - это архив двоичных файлов объектов (*.o), каждый из которых содержит реализацию набора функций, констант и т.д. Что касается вашего вопроса, то двоичный файл объекта, содержащий реализацию printf
(и все остальное в тот же файл объекта) будут связаны с вашей программой. Другие неиспользуемые файлы объектов будут исключены как оптимизация времени соединения.
__attribute__((section(...)))
-ffunction-section
не является параметром по умолчанию в gcc
(не является параметром по умолчанию при компиляции библиотек программирования), гранулярность раздела обычно выходит за рамки реализации одной функции. Таким образом, мы обычно не можем ожидать связывания вызываемой функции без введения избыточности, по крайней мере, в случае настольных компьютеров.
Файл заголовка содержит только объявление, например, формы
size_t printf(char const *format, ...);
Это говорит компилятору, что если он встречает слово printf
и используется как функция, он может генерировать вызов функции.
Инструкция вызова содержит местозаполнитель для фактического адреса функции, который затем вставляется компоновщиком при создании окончательного исполняемого файла.
Компилятор обычно просто ведет список запущенных до сих пор неразрешенных символов и добавляет в этот список, когда он встречает один из этих заполнителей, и заполняет фактический адрес, когда находит определение для символа. В случае с printf
это определение находится в стандартной библиотеке C, поэтому компоновщик обеспечивает загрузку библиотеки во время выполнения, а команда вызова - по правому адресу.