Как C / C ++ / Objective-C сравнивается с C #, когда дело доходит до использования библиотек?

2

Этот вопрос основан на предыдущем вопросе: Как получается компиляция С# вокруг файлов заголовков?.

Подтверждение того, что компиляция С# использует несколько проходов, по существу отвечает моему первоначальному вопросу. Кроме того, ответы показали, что С# использует метаданные подписи типа и метода, хранящиеся в сборках, для проверки синтаксиса кода во время компиляции.

Q:, как C/С++/ Objective-C знает, какой код загружать во время выполнения, связанное во время компиляции? И чтобы связать его с технологией, с которой я знаком, как это делает С#/CLR?

Исправьте меня, если я ошибаюсь, но для С#/CLR мое интуитивное понимание состоит в том, что определенные пути проверяются для сборок после выполнения, и в основном весь код загружается и динамически связывается во время выполнения.

Изменить: обновлено, чтобы включить С++ и Objective-C с помощью C.

Обновление: Чтобы выяснить, что мне действительно интересно, так это то, как компиляция C/С++/ Objective-C соответствует "внешнему определенному" символу в моем источнике с фактической реализацией этого кода, что это вывод компиляции, и в основном, как вывод компиляции выполняется микропроцессором для беспрепятственного прохождения управления в код библиотеки (с точки зрения указателя инструкции). Я сделал это с помощью виртуальной машины CLR, но мне любопытно узнать, как это работает концептуально в С++/Objective-C на реальном микропроцессоре.

Теги:
compiler-construction
clr

4 ответа

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

Компилятор играет важную роль в построении C/С++ для разрешения внешних зависимостей. Языки .NET не используют компоновщик.

Существует два вида внешних зависимостей: те, чья реализация доступна во время ссылки в другом файле .obj или .lib, предлагаемом в качестве входных данных для компоновщика. И те, которые доступны в другом исполняемом модуле. DLL в Windows.

Компонент разрешает первые в момент ссылки, ничего сложного не происходит, так как компоновщик будет знать адрес зависимости. Последний шаг зависит от платформы. В Windows компоновщик должен быть снабжен библиотекой импорта. Довольно простой файл, который просто объявляет имя DLL и список экспортированных определений в DLL. Компоновщик разрешает зависимость, введя скачок кода и добавляя запись во внешнюю таблицу зависимостей, которая указывает местоположение перехода, чтобы его можно было исправлять во время выполнения. Загрузка DLL и настройка таблицы импорта выполняется во время выполнения загрузчиком Windows. Это взгляд с высоты птичьего полета на процесс, есть много скучных деталей, чтобы сделать это как можно быстрее.

В управляемом коде все это выполняется во время выполнения, управляемое компилятором JIT. Он преобразует IL в машинный код, управляемый выполнением программы. Всякий раз, когда выполняется код, который ссылается на другой тип, компилятор JIT переходит в действие, загружает тип и переводит вызываемый метод типа. Побочным эффектом загрузки типа является загрузка сборки, содержащей этот тип, если она не была загружена раньше.

Примечательно также, что разница во внешних зависимостях, доступных во время сборки. Компилятор C/С++ компилирует один исходный файл за раз, зависимости решаются компоновщиком. Управляемый компилятор обычно принимает все исходные файлы, которые создают сборку как входную, а не компилируют их по одному. Фактически поддерживается отдельная компиляция и компоновка (.netmodule и al.exe), но она плохо поддерживается доступными инструментами и поэтому редко выполняется. Кроме того, он не может поддерживать такие функции, как методы расширения и частичные классы. Соответственно, для выполнения работы управляемому компилятору требуется еще много системных ресурсов. Легко доступны на современном оборудовании. Процесс сборки для C/С++ был создан в эпоху, когда эти ресурсы недоступны.

  • 0
    Очень интересно читать
  • 0
    Круто, спасибо (принято за включение сравнения CLR).
Показать ещё 2 комментария
3

Я считаю, что процесс, о котором вы спрашиваете, называется разрешением символа. В общем случае он работает в этом направлении (я старался сохранить его в нейтральной ОС):

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

  • Второй шаг - статическая привязка. Это также происходит во время компиляции. Во время статического процесса связывания все объектные файлы, созданные на первом шаге, и любые файлы статической библиотеки (которые являются только особым видом объектного файла) объединяются в один исполняемый файл. Статический компоновщик выполняет проход через символы, экспортируемые каждым объектным файлом и статической библиотекой, которую, как ему сказали, связывает вместе, и строит полный список экспортированных символов (и их значений). Затем он пропускает неразрешенные символы в каждом объектном файле и где символ находится в главном списке, заменяет все заполнители фактическим значением символа. Для любых символов, которые по-прежнему остаются неразрешенными в конце этого процесса, компоновщик просматривает список символов, экспортируемых всеми динамическими библиотеками, о которых он знает. Он создает список динамических библиотек, которые требуются, и сохраняет их в исполняемом файле. Если какие-либо символы еще не найдены, процесс связи не выполняется.

  • Третий шаг - это динамическое связывание, которое происходит во время выполнения. Динамический компоновщик загружает динамические библиотеки в список, содержащийся в исполняемом файле, и заменяет заполнители для оставшихся неразрешенных символов соответствующими значениями из динамических библиотек. Это может быть выполнено "с нетерпением" - после загрузки исполняемого файла, но до его запуска - или "лениво", которое по запросу, когда к нему разрешен неразрешенный символ.

  • 0
    Спасибо, очень информативно.
1

Стандартам C и С++ нечего сказать о загрузке во время выполнения - это полностью зависит от ОС. В случае Windows один связывает код с библиотекой экспорта (сгенерированный при создании DLL), который содержит имена функций и имя библиотеки DLL, в которой они находятся. Линкер создает заглушки в коде, содержащем эту информацию. Во время выполнения эти заглушки используются средой C/С++ вместе с Windows LoadLibrary() и связанными функциями для загрузки кода функции в память и ее выполнения.

  • 0
    Чтобы прояснить, что меня действительно интересует, так это то, как программа на C / C ++ / ObjC сопоставляет «внешне определенный» символ в одном исполняемом файле или библиотеке с фактической реализацией этого кода, и как это сравнивается с C # / CLR. Прошло много времени с тех пор, как я что-то скомпилировал в C / C ++ / ObjC. Итак, вы говорите, что для C / C ++, но кажется, что весь код зависимой библиотеки связан во время компиляции, и мы в итоге получаем один монолотный исполняемый файл? Но компиляция Windows делает особые вещи для достижения динамического связывания ...
  • 0
    В итоге получается один исполняемый файл, но часть исполняемого кода остается в отдельных библиотеках DLL. И да, в процессе компиляции / компоновки Windows есть «особые вещи», как это было бы в (например) процессе компиляции / компоновки Linux. Этот специальный материал не определен стандартами языка программирования.
0

В библиотеках вы ссылаетесь на библиотеки DLL?

Существуют определенные шаблоны для ОС для поиска необходимых файлов (обычно они начинаются с локального пути приложения, а затем переходят к папке с указанием переменной окружения <PATH> .)

  • 0
    Не обязательно. Возможно, я использую неправильную терминологию. Я в первую очередь разработчик C #, и многое из этого удалено благодаря CLR. Под «библиотеками» я имею в виду уже существующий код (в C / C ++ / ObjC это предварительно скомпилированный объектный код; в CLR это предварительно скомпилированный байт-код).

Ещё вопросы

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