Как компоновщик (ld) знает в системе компиляции, с кем связывать myprogram.o? - PullRequest
0 голосов
/ 21 января 2019

Я недавно прочитал CSAPP, и у меня были некоторые сомнения по поводу его части системы компиляции.

Теперь у нас есть пример, использующий HelloWorld.c (просто напечатайте hello world). В книге говорится, что на этапе предварительной обработки они заменяют строку «#include» содержимым этого заголовочного файла. Но когда я открываю stdio.h, я обнаруживаю, что существует только объявление для printf (), и нет конкретной реализации. Итак, когда в системе компиляции будет введена конкретная реализация printf ()?

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

Ответы [ 5 ]

0 голосов
/ 21 января 2019

Практически, упрощенно:

  • Вы можете скомпилировать функцию в библиотеку (например, .a или .so файл в Unix).
  • Библиотека имеет тело функции (инструкции по сборке) и имя функции. Ex. библиотека libc.so имеет функцию printf, которая начинается с символа 0xaabbccdd в файле библиотеки libc.so.
  • Вы хотите скомпилировать вашу программу.
  • Вам нужно знать, какие аргументы printf принимает. Это займет int? Это займет char *? Это займет uint_least64_t? Это в заголовочном файле - int printf(const char *, ...);. Заголовок сообщает компилятору, как вызывать функцию (какие параметры принимает функция и какой тип она возвращает). Обратите внимание, что каждый файл .c компилируется отдельно.
  • Объявление функции (какие аргументы принимает функция и что она возвращает) не сохраняется в файле библиотеки. Хранится в шапке (только). Библиотека имеет имя функции (только printf) и тело скомпилированной функции. Заголовок имеет int printf(const char *, ...); без тела функции.
  • Вы компилируете свою программу. Компилятор генерирует код, чтобы аргументы правильного размера помещались в стек. И из стека ваш код берет переменную, возвращенную из функции. Теперь ваша программа скомпилирована в сборку, которая выглядит как push pointer to "%d\n" on the stack; push some int on the stack; call printf; pop from the stack the returned "int"; rest of the instructions;.
  • Линкер просматривает вашу скомпилированную программу и видит call printf. Затем он говорит: «Ох, в вашем коде нет тела printf». Затем он ищет printf в библиотеках, чтобы увидеть, где он находится. Компоновщик просматривает все библиотеки, с которыми вы связываете свою программу, и находит printf в стандартной библиотеке - он находится в libc.so по адресу 0xaabbccdd. Так что компоновщик заменяет call printf на goto libs.so file to address 0xaabbccdd своего рода инструкцию.
  • После того, как все «символы» (т. Е. Имена функций, имена переменных) «разрешены» (компоновщик их где-то нашел), вы можете запустить свою программу. call printf перейдет в файл libc.so в указанном месте.

То, что я написал выше, предназначено только для иллюстрации.

0 голосов
/ 21 января 2019

По умолчанию библиотека (содержащая реализацию printf) каждый раз связана в вашей программе на Си.

Включая заголовки, вы просто указываете (на данный момент) во время компиляции, что реализации объявленных функций(внутри заголовка) где-то еще.И позже на этапе компоновки эти реализации функций «добавляются» в ваш код.

0 голосов
/ 21 января 2019

Почему компоновщик знает, как связать мой объектный файл с printf.o

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

почему он объявляет эту функцию на первом шаге ...

Чтобы узнать об этом.

... и связать конкретную реализациюна последнем шаге

Поскольку нет необходимости делать это раньше.

0 голосов
/ 21 января 2019

Все стандарты C и C ++ говорят вам, что вам нужно #include данный заголовочный файл для того, чтобы представить некоторые функциональные возможности (на некоторых платформах, которые могут даже не понадобиться, хотя включение - хорошая идея с тех пор, как вынаписание portable code).

Это дает компиляторам большую гибкость.

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

0 голосов
/ 21 января 2019

Почему компоновщик знает, как связать мой объектный файл с printf.o?

LD знает, как искать и находить их.Вы можете увидеть с man ld.so :

Если зависимость общего объекта не содержит косую черту, то она ищется в следующем порядке:

  • Использование каталогов, указанных в атрибуте динамического раздела DT_RPATH двоичного файла, если атрибут присутствует и атрибут DT_RUNPATH не существует.Использование DT_RPATH устарело.
  • Использование переменной среды LD_LIBRARY_PATH, если только исполняемый файл не запущен в режиме безопасного выполнения (см. Ниже), в этом случае эта переменная игнорируется.
  • Использованиекаталоги, указанные в атрибуте динамического раздела DT_RUNPATH двоичного файла, если он есть.Такие каталоги ищутся только для поиска тех объектов, которые требуются для записей DT_NEEDED (прямые зависимости), и не применяются к дочерним объектам этих объектов, которые сами должны иметь свои собственные записи DT_RUNPATH.В отличие от DT_RPATH, который применяется для поиска всех дочерних элементов в дереве зависимостей.
  • Из файла кэша /etc/ld.so.cache, который содержит скомпилированный список общих объектов-кандидатов, ранее найденных впуть расширенной библиотеки.Однако если двоичный файл был связан с параметром компоновщика -z nodeflib, общие объекты в путях по умолчанию пропускаются.Общие объекты, установленные в каталогах возможностей оборудования (см. Ниже), предпочтительнее других общих объектов.
  • В пути по умолчанию / lib, а затем / usr / lib.(На некоторых 64-разрядных архитектурах пути по умолчанию для 64-разрядных общих объектов - / lib64, а затем / usr / lib64.) Если двоичный файл был связан с параметром компоновщика -z nodeflib, этот шаг пропускается.

Почему в системе компиляции она объявляет эту функцию на первом этапе (фаза препроцессора) и связывает конкретную реализацию на последнем этапе (фаза компоновки)?

На этапе компиляции вам нужно знать , на что вы будете ссылаться и компилировать соответственно, поэтому для него нужно прочитать файлы .h с определением.На этапе связывания требуются только файлы .o.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...