Организация ЭВМ и систем: Курс лекций (Позиционные системы счисления. Процессоры семейства IA-32. Лазерные принтеры), страница 62

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

Практически это выполняется при помощи такой последовательности действий:

1)  построение таблицы объектных модулей и их длин;

2)  приписывание начальных адресов каждому объектному модулю;

3)  поиск команд с явно заданными адресами и прибавление к каждому из адресов начального смещения модуля;

4)  замена символических ссылок реальными адресами.

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

Обычно, объектные модули состоят, как минимум, из шести частей:

1)  заголовок;

2)  список идентификаторов, доступных для внешних ссылок на них, и их значений или адресов в модуле;

3)  список идентификаторов, не определённых в данном модуле, но используемых в нём, и адреса ссылок в данном модуле;

4)  тело сегментов данных, кода и других: машинные команды и константы, — это единственная часть объектного модуля, которая будет загружаться для выполнения;

5)  список настраиваемых адресов, который содержит перечень всех адресов, к которым должно быть прибавлено начальное смещение модуля;

6)  заключительная часть, которая в зависимости от реализации может содержать адреса точек входа в процедуры, контрольные суммы и другую вспомогательную информацию.

Большинство компоновщиков работает в два прохода. На первом проходе строится таблица объектных модулей со всей необходимой информацией о них. На втором, модули размещаются последовательно и преобразовываются должным образом.

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

Следует заметить, что статическое связывание обеспечивает достаточно высокую гибкость при сегментированной памяти. Действительно, базовые адреса сегментов в коде программы не задаются и потому могут быть произвольно назначены в процессе загрузки. Таким образом, отдельные сегменты могут загружаться произвольным образом.

22.2.  Динамическое связывание

Нет причин, по которым исполнимый код полностью, от начала и до конца должен быть построен до начала выполнения программы. Здесь уместно вспомнить, что интерпретаторы вообще «строят» исполнимый код только на один оператор исходного текста, который сейчас будет выполняться. Но и в транслируемых программах есть множество вариантов для времени связывания объектных модулей. Например:

1)  при написании программы;

2)  при трансляции;

3)  при компоновке (до загрузки);

4)  при загрузке программы для выполнения;

5)  при загрузке базовых регистров, используемых для адресации;

6)  при выполнении команды, обращающейся к памяти.

Первые три способа относятся к статическому связыванию, причём первые два используются крайне редко ввиду их трудоёмкости. Остальные три — к динамическому связыванию.

Динамическое связывание сложнее для реализации, чем статическое. К нему прибегают из-за следующих недостатков последнего:

1)  принципиальная невозможность определить физические адреса заранее при виртуальной страничной организации памяти (невозможно заранее определить, куда будет загружена или перемещена та или иная страница);

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

Наибольшую гибкость даёт определение адреса при (первом) выполнении команды, обращающейся к памяти.