Что делают компоновщики?

Я всегда задавался вопросом. Я знаю, что компиляторы преобразовывают код, который Вы пишете в двоичные файлы, но что делают компоновщики? Они всегда были тайной мне.

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

Кто-то мог объяснить условия?

110
задан Kristina Brooks 23 July 2010 в 22:44
поделиться

3 ответа

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

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

После создания объектного файла в дело вступает компоновщик. Чаще всего реальная программа, которая делает что-либо полезное, должна ссылаться на другие файлы. Например, в языке C простая программа для вывода вашего имени на экран будет состоять из следующих элементов:

printf("Hello Kristina!\n");

Когда компилятор скомпилировал вашу программу в файл obj, он просто поместил ссылку на функцию printf. Компоновщик разрешает эту ссылку. Большинство языков программирования имеют стандартную библиотеку процедур, охватывающую основные вещи, ожидаемые от этого языка. Компоновщик связывает ваш OBJ-файл с этой стандартной библиотекой. Компоновщик также может связать ваш OBJ-файл с другими OBJ-файлами. Вы можете создавать другие OBJ-файлы, содержащие функции, которые могут быть вызваны другим OBJ-файлом. Компоновщик работает почти как копирование и вставка в текстовом процессоре. Он "копирует" все необходимые функции, на которые ссылается ваша программа, и создает единый исполняемый файл. Иногда другие библиотеки, которые копируются, зависят от других OBJ или библиотечных файлов. Иногда компоновщику приходится быть довольно рекурсивным, чтобы выполнить свою работу.

Обратите внимание, что не все операционные системы создают один исполняемый файл. В Windows, например, используются библиотеки DLL, в которых все эти функции собраны в одном файле. Это уменьшает размер исполняемого файла, но делает его зависимым от этих специфических DLL. В DOS использовались вещи, называемые оверлеями (файлы .OVL). Это имело много целей, но одна из них заключалась в том, чтобы держать часто используемые функции вместе в одном файле (другой целью, если вам интересно, была возможность помещать большие программы в память. DOS имеет ограничение по памяти, и оверлеи могли "выгружаться" из памяти, а другие оверлеи могли "загружаться" поверх этой памяти, отсюда и название "оверлеи"). В Linux есть общие библиотеки, что, по сути, является той же идеей, что и DLL-библиотеки (знатоки Linux скажут мне, что есть МНОГО БОЛЬШИХ различий).

Надеюсь, это поможет вам понять!

138
ответ дан 24 November 2019 в 03:13
поделиться

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

Что делает компоновщик, так это рассматривает все модули вместе, смотрит на то, что каждому модулю нужно подключить к чему-то вне себя, и смотрит на все экспортируемые им вещи. Затем он исправляет все это и создает окончательный исполняемый файл, который затем можно запустить.

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

15
ответ дан 24 November 2019 в 03:13
поделиться

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

Затем он объединяет все эти объектные файлы вместе и присваивает адреса каждому из символов, и там, где один объектный файл имеет внешнюю ссылку на другой объектный файл, он заполняет адрес каждого символа везде, где он используется другим объектом. В типичном случае он также создает таблицу всех используемых абсолютных адресов, так что загрузчик может/будет "исправлять" адреса при загрузке файла (т.е. добавлять базовый адрес загрузки к каждому из этих адресов, чтобы все они ссылались на правильный адрес памяти).

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

10
ответ дан 24 November 2019 в 03:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: