- •Раздел 1 Системы программирования
- •Тема 1.1 Принципы функционирования систем программирования
- •Тема 1.2 Трансляторы, компиляторы и интерпретаторы — общая схема работы
- •Тема 1.3 Таблицы идентификаторов. Организация таблиц идентификаторов
- •Тема 1.4 Назначение и функции компоновщика
- •Тема 1.5 Назначение и функции программы загрузчика
- •Тема 1.6 Программные отладчики и их эксплуатация
Тема 1.4 Назначение и функции компоновщика
Компоновщик (или редактор связей) предназначен для связывания между собой объектных файлов, порождаемых компилятором, а также файлов библиотек, входящих в состав системы программирования. Название «компоновщик» происходит от англ. «link» (связывать, сцеплять). Во многих системах программирования файл компоновщика носит имя «linker», а разработчики на жаргонном языке нередко называют его «линковщик», или «линкер».
Объектный файл (или набор объектных файлов) не может быть исполнен до тех пор, пока все модули и секции не будут в нем увязаны между собой. Это и делает редактор связей (компоновщик). Результатом его работы является единый файл (часто называемый «исполняемым файлом»), который содержит весь текст результирующей программы на языке машинных кодов.
Задача компоновщика проста — он должен пройти весь код результирующей программы, начиная от места вызова ее главной исполняемой функции (точки входа) и до конца (точки выхода), найти все вызовы внешних процедур и функций, обращения к внешним переменным и увязать их с кодом других модулей, где описаны эти процедуры, функции и переменные. Причем в этих модулях, в свою очередь, могут быть обращения к другим внешним функциям — и т. д. Функции, описанные в разных модулях исходной программы, могут вызывать друг друга сколь угодно раз — компоновщик должен найти соответствие каждому из этих вызовов и определить («разрешить») соответствующую ссылку (адрес). Отсюда и происходит другое название компоновщика — «редактор связей».
Ссылки (вызовы процедур или функций, обращения к переменным), которым компоновщик не смог найти соответствие, называются «неразрешенными»1. В готовой к выполнению результирующей программе не должно быть неразрешенных ссылок. Компоновщик порождает сообщение об ошибке, если при попытке собрать объектные файлы в единое целое он не смог обнаружить какой-либо необходимой составляющей.
Компоновщик начинает свою работу с того, что выбирает из первого объектного модуля программную секцию и присваивает ей начальный адрес. Программные секции остальных объектных модулей получают адреса относительно начального адреса в порядке следования. При этом может выполняться также функция выравнивания начальных адресов программных секций. Одновременно с объединением текстов программных секций объединяются секции данных, таблицы идентификаторов и внешних имен. Разрешаются межсекционные ссылки.
Процедура разрешения ссылок сводится к вычислению значений адресных констант процедур, функций и переменных с учетом перемещений секций относительно начала собираемого программного модуля. Если при этом обнаруживаются ссылки на внешние переменные, отсутствующие в списке объектных модулей, редактор связей организует их поиск в библиотеках, доступных в системе программирования. Если же и в библиотеке необходимую составляющую найти не удается, формируется сообщение об ошибке. То же самое может произойти, если описание некоторой переменной, процедуры или функции будет обнаружено более чем в одном объектном файле среди обрабатываемых компоновщиком. Тогда компоновщик не сможет определить, к какому из них относится ссылка2.
В современных системах программирования компоновщик включает в состав исполняемого файла не только код объектных модулей, который подготовил компилятор исходной программы, но и описание ресурсов пользовательского интерфейса, которое готовит компилятор ресурсов.
Как и компилятор при распределении памяти, компоновщик работает с относительными адресами переменных, процедур и функций — условными единицами, отсчитываемыми от начала программы. Так же как и компилятор, он не может знать адресов памяти, в которых будет выполняться результирующая программа.
Компоновщик работает с теми именами процедур и функций, которые им присвоил компилятор в таблице идентификаторов. Эти имена, как уже было сказано ранее, могут отличаться от имен, данных пользователем. Поэтому если два или более обращений к одной и той же функции или переменной в исходном тексте программы выполнены по-разному, компилятор может дать им разные имена и ссылка останется неразрешенной, что приведет к сообщению об ошибке от компоновщика (см. раздел, посвященный идентификации имен переменных и функций в данном учебном пособии). Так исходный код программы может влиять на работу компоновщика и порождать ошибки, обнаруживаемые системой программирования только на этапе компоновки результирующей программы.
Другая возможная ошибка на этапе компоновки — несоответствие между объектным файлом и файлом описания процедуры или функции. Дело в том, что компилятор порождает вызовы процедур и функций исходя из их описания. Если функция описана в одном из исходных файлов, то он может проверить соответствие ее описания и исходного кода, но если обращение выполняется к библиотечной функции, то такая возможность отсутствует (библиотеки функций поставляются, как правило, в виде объектного кода). В том случае, когда имена функций совпадают, но описание не соответствует реальному объектному коду, может возникнуть трудно обнаруживаемая ошибка — такую ошибку можно отследить только при отладке результирующей программы. Конечно же, такие ошибки крайне редко встречаются в библиотеках систем программирования и других библиотеках процедур и функций, поставляемых известными производителями.
Компоновщик — это достаточно простой компонент в составе системы программирования. От него не зависит эффективность результирующей программы, но ее объем может зависеть от типа компоновщика. В простейшем случае при обнаружении ссылки на некоторый объектный файл компоновщик подключает к исполняемому файлу весь код этого объектного файла и все его данные. Тогда алгоритм работы компоновщика прост, но в этом варианте мы получаем исполняемый файл результирующей программы, который может содержать некоторую часть объектного кода, к которой никогда не будет обращений при выполнении программы. В более сложном случае компоновщик при обнаружении ссылки подключает к исполняемому файлу не весь код объектного файла, а только ту его часть, которая связана с разрешаемой ссылкой. В этом случае исполняемый файл результирующей программы будет иметь меньший объем, но код компоновщика усложнится, так как ему нужно будет отслеживать не только внешние, но и внутренние ссылки. Однако это незначительное усложнение, и, как правило, все современные системы программирования оснащены компоновщиками второго типа.
Обычно компоновщик формирует простейший программный модуль, создаваемый как единое целое. Однако в общем случае компоновщик может создавать и другие модули: программные модули с оверлейной структурой, объектные модули библиотек и модули динамически подключаемых библиотек.
