Директивы препроцессора и связывание

0

Директива #include приведет к размещению содержимого файла заголовка в исходном коде перед компиляцией: например, если я включил stdio.h, препроцессор будет работать над размещением всего содержимого stdio.h в исходном коде, тогда компиляция isn Не так ли?

Поэтому давайте предположим, что я просто использую функцию printf() в своем коде. Таким образом, должно произойти что-то, что произойдет после компиляции и между связыванием, которое удалит все реализации функций, которые были включены из файла заголовка, и только вставьте реализацию функции printf() в таблицу импорта исполняемого файла, зная, что все остальные функции были скомпилированы с источником. Вы можете объяснить это мне, пожалуйста?

  • 3
    Дело не в том, что другие функции удалены. Если вы не вызываете функцию, не генерируется код, который ссылается на нее, поэтому связь отсутствует из-за того, что она никогда не создавалась. Это все равно что сказать: «Телефонная компания прислала мне телефонную книгу. Я посмотрел на своего друга и позвонил ему. Когда я получаю свой счет за телефон, я вижу в списке только моего друга. Кто удаляет все телефонные номера, на которые я не звонил ?»
  • 0
    @RaymondChen, не совсем. Оптимизация во время компоновки устранит неиспользуемый код для каждого файла объекта. Что касается вашего примера, телефонный счет может фактически содержать записи для всех членов семьи.
Показать ещё 8 комментариев
Теги:

3 ответа

3
Лучший ответ

Функция printf фактически не находится в файле заголовка, она находится в библиотеке, которая автоматически связана с исполняемым файлом компоновщиком.

Заголовочные файлы должны содержать только прототипы функций, т.е. Объявление функций, а не определений. И поскольку в заголовочном файле нет определений функций, для них фактически не будет создан код, и компилятор будет удостовериться, что только созданные вами функции получат запись в сгенерированном объектном файле, чтобы линкер знал об этом.

  • 0
    Таким образом, задача компоновщика состоит в том, чтобы включить код для этих функций, чтобы сгенерировать исполняемый файл, верно?
  • 0
    @JohnnyCat Да, это так. Он берет все объектные файлы, сгенерированные компилятором, и все библиотеки, и объединяет их в один исполняемый файл.
Показать ещё 3 комментария
1

Прототип функции, объявленный в файле заголовка C, действует как ограничение времени компиляции. Он определяет правильное количество параметров и правильные типы этих параметров. Проверка таких ограничений происходит во время фазы компиляции (т.е. *.c → *.o).

Статическая фаза связывания исключает неиспользуемые двоичные объекты из библиотек программирования для каждого объекта. Библиотека программирования - это архив двоичных файлов объектов (*.o), каждый из которых содержит реализацию набора функций, констант и т.д. Что касается вашего вопроса, то двоичный файл объекта, содержащий реализацию printf (и все остальное в тот же файл объекта) будут связаны с вашей программой. Другие неиспользуемые файлы объектов будут исключены как оптимизация времени соединения.

  • 1
    Исключение обычно выполняется для каждого раздела, а не для объектного файла. Можно воспользоваться этим, используя что-то вроде __attribute__((section(...)))
  • 0
    @anatolyg, я согласен, что ссылки технически для каждого раздела. но так как -ffunction-section не является параметром по умолчанию в gcc (не является параметром по умолчанию при компиляции библиотек программирования), гранулярность раздела обычно выходит за рамки реализации одной функции. Таким образом, мы обычно не можем ожидать связывания вызываемой функции без введения избыточности, по крайней мере, в случае настольных компьютеров.
0

Файл заголовка содержит только объявление, например, формы

size_t printf(char const *format, ...);

Это говорит компилятору, что если он встречает слово printf и используется как функция, он может генерировать вызов функции.

Инструкция вызова содержит местозаполнитель для фактического адреса функции, который затем вставляется компоновщиком при создании окончательного исполняемого файла.

Компилятор обычно просто ведет список запущенных до сих пор неразрешенных символов и добавляет в этот список, когда он встречает один из этих заполнителей, и заполняет фактический адрес, когда находит определение для символа. В случае с printf это определение находится в стандартной библиотеке C, поэтому компоновщик обеспечивает загрузку библиотеки во время выполнения, а команда вызова - по правому адресу.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню