Однако, в объектном модуле обращения к элементам, расположенным в других модулях, например, вызов процедуры из другого модуля, записаны в виде символической ссылки. То есть в объектном модуле вместо реальных адресов стоят идентификаторы (иногда в кодированном виде). Первейшая задача компоновщика состоит в замене таких символических ссылок на реальные адреса и коррекции явно определённых в объектном модуле адресов, которые изменяются вследствие размещения нескольких модулей в одном сегменте.
Практически это выполняется при помощи такой последовательности действий:
1) построение таблицы объектных модулей и их длин;
2) приписывание начальных адресов каждому объектному модулю;
3) поиск команд с явно заданными адресами и прибавление к каждому из адресов начального смещения модуля;
4) замена символических ссылок реальными адресами.
Чтобы компоновщик мог справиться с поставленными задачами, объектный модуль должен, помимо исходного кода, содержать вспомогательную информацию.
Обычно, объектные модули состоят, как минимум, из шести частей:
1) заголовок;
2) список идентификаторов, доступных для внешних ссылок на них, и их значений или адресов в модуле;
3) список идентификаторов, не определённых в данном модуле, но используемых в нём, и адреса ссылок в данном модуле;
4) тело сегментов данных, кода и других: машинные команды и константы, — это единственная часть объектного модуля, которая будет загружаться для выполнения;
5) список настраиваемых адресов, который содержит перечень всех адресов, к которым должно быть прибавлено начальное смещение модуля;
6) заключительная часть, которая в зависимости от реализации может содержать адреса точек входа в процедуры, контрольные суммы и другую вспомогательную информацию.
Большинство компоновщиков работает в два прохода. На первом проходе строится таблица объектных модулей со всей необходимой информацией о них. На втором, модули размещаются последовательно и преобразовываются должным образом.
Описанный сейчас подход называется статическим связыванием (объектных модулей), ибо исполнимый модуль полностью строится до начала выполнения программы и не может изменён в дальнейшем. Такой исполнимый модуль должен быть помещён именно по тому адресу, который планировался при компоновке.
Следует заметить, что статическое связывание обеспечивает достаточно высокую гибкость при сегментированной памяти. Действительно, базовые адреса сегментов в коде программы не задаются и потому могут быть произвольно назначены в процессе загрузки. Таким образом, отдельные сегменты могут загружаться произвольным образом.
Нет причин, по которым исполнимый код полностью, от начала и до конца должен быть построен до начала выполнения программы. Здесь уместно вспомнить, что интерпретаторы вообще «строят» исполнимый код только на один оператор исходного текста, который сейчас будет выполняться. Но и в транслируемых программах есть множество вариантов для времени связывания объектных модулей. Например:
1) при написании программы;
2) при трансляции;
3) при компоновке (до загрузки);
4) при загрузке программы для выполнения;
5) при загрузке базовых регистров, используемых для адресации;
6) при выполнении команды, обращающейся к памяти.
Первые три способа относятся к статическому связыванию, причём первые два используются крайне редко ввиду их трудоёмкости. Остальные три — к динамическому связыванию.
Динамическое связывание сложнее для реализации, чем статическое. К нему прибегают из-за следующих недостатков последнего:
1) принципиальная невозможность определить физические адреса заранее при виртуальной страничной организации памяти (невозможно заранее определить, куда будет загружена или перемещена та или иная страница);
2) невозможность априорного связывания пользовательских процедур с динамически загружаемыми библиотеками (невозможно заранее определить, куда будет загружен модуль такой библиотеки).
Наибольшую гибкость даёт определение адреса при (первом) выполнении команды, обращающейся к памяти.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.