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

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

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

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

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

Редактировать: Обновлен для включения C ++ и Objective-C с C.

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

Ответы [ 4 ]

6 голосов
/ 17 декабря 2009

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

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

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

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

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

3 голосов
/ 17 декабря 2009

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

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

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

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

1 голос
/ 17 декабря 2009

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

0 голосов
/ 17 декабря 2009

По библиотекам вы имеете в виду DLL, верно?

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

...