Редактор связей[1] выполняет две функции. Во-первых, как можно заключить по его названию, он комбинирует (компонует, редактирует) различные объектные файлы. Вторая его функция — разрешать адреса вызовов и инструкций загрузки, найденных в редактируемых объектных файлах. Чтобы понять принцип работы редактора связей, рассмотрим подробнее процесс раздельной компиляции.
Раздельная компиляция — это возможность, позволяющая разбить программу на несколько файлов, скомпилировать каждый из этих файлов отдельно, а потом скомпоновать[2] их, чтобы в конечном итоге создать исполняемый файл[3]. Результатом работы компилятора является объектный файл, а результатом работы редактора связей — исполняемый файл. Редактор связей физически связывает файлы, внесенные в список компоновки, в один программный файл и разрешает внешние ссылки. Внешняя ссылка создается каждый раз, когда программа из одного файла ссылается на код из другого файла. Это происходит при вызове функции и при ссылке на глобальную переменную. Например, при компоновке двух приведенных ниже файлов, должна быть разрешена ссылка в файле 2 на идентификатор count, объявленный в файле 1. Редактор связей сообщает программе из файла 2, где найти count.
Файл 1 Файл 2 int count; #include <stdio.h> void display(void); extern int count; int main(void) void display(void) { { count = 10; printf("%d", count); display(); } return 0; }
Аналогично, редактор связей укажет файлу 1, где находится функция display(), чтобы можно было ее вызвать.
При генерации объектного кода функции display(), компилятор подставляет в негo вместо адреса идентификатора count "заполнитель", т.е. ссылку на внешнее имя, потому что он не располагает информацией о том, где находится count. Нечто подобное происходит при компиляции main(). Адрес функции display() не известен, поэтому вместо него используется "заполнитель", т.е. ссылка на внешнюю программу. При компоновке этих двух файлов содержащиеся в них внешние ссылки заменяются адресами соответствующих элементов. Являются ли эти адреса абсолютными или переместимыми, — зависит от среды[4].
В результате работы редактора связей для большинства видов вычислительной среды получается переместимый код. Так называют объектный код, который может работать в любой свободной области памяти, способной его уместить. В переместимом объектном файле адрес каждой инструкции вызова или загрузки является не фиксированным, а относительным. Таким образом, адреса в переместимом коде отсчитываются от адреса начала программы. При загрузке программы в память для выполнения, загрузчик преобразует относительные адреса в физические адреса, соответствующие адресам ячеек памяти, в которую загружается программа.
В некоторых вычислительных средах, таких как специализированные устройства управления, в которых для всех программ используется одно и то же адресное пространство, редактор связей подставляет в конечный результат своей работы физические адреса. В этом случае он генерирует абсолютный код[5].
Хотя в наше время эта возможность применяется редко, следует отметить, что компиляторы С некоторых вычислительных сред в дополнение к обычным компоновщикам предоставляют компоновщики оверлеев. Компоновщик оверлеев работает так же как и обычный, но он также может создавать оверлеи[6]. Оверлей — это фрагмент объектного[7] кода, который хранится в файле на диске и загружается для работы только по мере необходимости. Место в памяти, которое отводится для загрузки оверлеев, называется оверлейной областью памяти. Оверлеи позволяют создавать и запускать программы, которые занимали бы большую область памяти, чем имеющаяся в наличии, потому что в каждый момент времени в памяти находится только та часть программы, которая нужна.
Чтобы понять, как работают оверлеи, представим, что имеется программа, состоящая из семи объектных файлов, которые называются F1, F2, ..., F7. Допустим, имеющейся свободной памяти недостаточно для загрузки программы, скомпонованной обычным образом из всех объектных файлов. Есть возможность скомпоновать только первые пять файлов, а при большем количестве наступит переполнение памяти. Выйти из подобной ситуации можно, дав компоновщику команду создать оверлеи из файлов F5, F6 и F7. При каждом вызове функции, которая содержится в одном из этих, файлов, администратор оверлейной загрузки (программа, предоставляемая компоновщиком или редактором связей) находит необходимый файл и помещает его в оверлейную область памяти, создавая условия для работы программы. Коды, которые получились при компиляции файлов F1 — F4, остаются резидентными. Данная схема проиллюстрирована на рис. 12.1.
+----+ +----------------+ | F5 |---. | F1 | +----+ | | . | F5...F7 | | . | загружаются +----+ | | . | по мере | F6 |-. | | F4 | необходимости +----+ | | +----------------+ | `-->| Оверлейная | +----+ `---->| | | F7 |------>| область памяти | +----+ +----------------+ |
Как читатель, возможно, уже догадался, принципиальным преимуществом оверлеев является возможность создавать с их помощью очень большие программы. Основной же их недостаток и причина редкого использования состоит в том, что процесс загрузки занимает определенное время и в значительной мере влияет на быстродействие программы. Поэтому при использовании оверлеев следует взаимосвязанные между собой функции группировать вместе, чтобы свести к минимуму число загрузок оверлеев.
Например, если приложение обрабатывает список рассылки, то имеет смысл поместить все подпрограммы сортировки в один оверлей, подпрограммы печати — в другой и т.д.
Как уже было сказано, в современных вычислительных средах оверлеи применяются редко.
Операционная система Windows предоставляет другой вид связывания, так называемое динамическое связывание. Динамическое связывание — это процесс, при котором объектный код функции остается в отдельном файле на диске до тех пор, пока не запустится использующая его программа. При запуске такой программы динамически загружаются затребованные, связанные с ней функции. Динамически связанный функции помещаются в специальный тип библиотек, которые называются динамически подсоединяемыми библиотеками (DLL — Dynamic-Link Library).
Основным преимуществом таких библиотек является возможность значительно сократить размер исполняемых программ, потому что отпадает необходимость в том, чтобы каждая программа содержала в себе копию используемых библиотечных функций. Другим положительным моментом является то, что при обновлении функций DLL, использующие их программы автоматически используют и все улучшения новых версий.
Стандартная библиотека С не содержится в динамически подсоединяемой библиотеке, но многие другие типы функций там есть. Например, при написании приложений для Windows, в DLL хранится полный набор функций программного интерфейса приложений (API — Application Program Interface). Нужно отметить, что для программы, написанной на языке С, обычно не имеет значения, хранятся ли библиотечные функции в DLL или в обычном файле библиотек.
[1]Иногда называется также компоновщиком. Впрочем, компоновщиками обычно называют программы, обладающие несколько меньшими возможностями, чем развитые редакторы связей.
[2]Этот процесс называется редактированием связей.
[3]Называется также загрузочным модулем.
[4]Редакторы связей обычно располагают широким набором возможностей, и при необходимости пользователь может указать необходимые параметры.
[5]Называется также программой в абсолютных адресах.
[6]Создание оверлейных программ — стандартная функция для редакторов связей. Кроме того редакторы связей обычно могут создавать даже загрузочные модули, загружаемые "вразброс", т.е. в несмежные участки памяти. Все это имеет огромное значение для систем, в которых отсутствует виртуальная память. Но в системах с виртуальной памятью эти возможности часто выглядят как ненужные излишества.
[7]Согласно оригиналу. Все же точнее часть загрузочного модуля.