А.В. Ахо, М.С. Лам, Р. Сети, Дж. Д. Ульман - Компиляторы - принципы, технологии и инструментарий (1114947), страница 105
Текст из файла (страница 105)
522 Глава 6. Генерация промежуточного кода В середине 1950-х годов появился ()ЯСОНА (()п(чегза! Сошр(!ег Опепгед ).ап8пайе — универсальный компиляторно-ориентированный язык), мифический универсальный промежуточный язык, при использовании которого компилятор мог строиться путем объединения начальной стадии для данного исходного языка программирования и заключительной стадии для данного целевого языка (10). Методы раскрутки (Ьоогзггарр!п8) описаны в сообщении (10) и регулярно используются для переноса компиляторов.
Идеал (ЛМСО)., заключающийся в связывании начальных и заключительных стадий компилятора, может быть достигнут различными способами. Переносимый компилятор состоит из одной начальной стадии, которая может объединяться с несколькими заключительными стадиями для реализации данного языка программирования на нескольких разных машинах. Ранним примером языка с переносимым компилятором, написанным на самом этом языке, является Ыейас (5).
Другой подход заключается в модификации начальной стадии нового языка для использования в существующем компиляторе. Фельдман (Ре!дшап) (2) описал добавление начальной стадии Рогггап 77 к компиляторам С (6 и 9). ОСС (ОЫ(3 Сошрйег СоПесйоп — набор компиляторов ОНО) [3] поддерживает начальные стадии для С, С-н-, ОЬ)есйче-С, Ропгап, )ача и Аг)а. Номера значений и их реализация путем хеширования разработаны Ершовым (1). Использование информации о типах для повышения безопасности байт-кода 1ача описано Гослингом (Оозйпй) (4). Выведение типов путем применения унификации для решения множеств уравнений было открыто несколько раз; его применение к М) описано Милнером (Мйпег) (7).
Всестороннее рассмотрение типов можно найти у Пирса (Р)егсе) (8). 1. ЕгаЬоч, А. Р., "Оп ргойгапппш8 оГ апйгпейс орегаг!опа", Сотт. АСМ 1:8 (1958), рр. 3 — 6. См. также Сотт. АСМ 1;9 (1958), р. ! 6. 2. Ре1дшап, К. 1., "1гпр!ешепгайоп оГ а роггаЫе Рогггап 77 сошрйег пгйп8 пюдегп гоо!а", АСМ Б!БР1АМ /чог/сез 14:8 (1979), рр. 98 — 106. 3. Начальная страница ОСС Ьсср: //г3сс. спи.
огд/, Ргее 5ойччаге Роипдаг)оп. 4. Оозйпй, 1., "1ача 1пГеппейаГе Ьу1есодез", Ргос. АСМ 316Р/А/ч' Иоп(хлор оп 1лгегтейаге Кергезепгаг/оле (1995), рр. 111-118. 5. Нпз)геу, Н. О., М. Н. На!згеаг), апд К. МсАгйпг, "Ыейас — а гйа!есс оГА18оГ', Сотт. АСМ 3:8 (1960), рр. 463-468. 6. 1оЬпзоп, 8. С., "А гонг йгои8Ь йе ропаЫе С сошрйег", Вей Те!ерЬопе ЬаЬ- огагопез, 1пс., Мштау НП1, Ы. 1., 1979. ГЛАВА 7 Среды времени выполнения Компилятор должен точно реализовывать абстракции, воплощенные в определении исходного языка программирования.
Обычно эти абстракции включают рассмотренные в разделе 1.6 концепции, такие как имена, области видимости, связывание, типы данных, операторы, процедуры, параметры и конструкции потока управления. Компилятор должен сотрудничать с операционной системой и другим программным обеспечением для поддержки этих абстракций на целевой машине. Для этого компилятор создает среду времени выполнения (гцп-Йпе епч1гопшеп1), в которой, как предполагается, будет выполняться целевая программа, и управляет ею. Эта среда решает множество вопросов, таких как схема размещения и память для именованных объектов исходной программы, механизмы, используемые целевой программой для доступа к переменным, связи между процедурами, механизмы передачи параметров, взаимодействие с операционной системой, устройствами ввода-вывода и другими программами.
Две главные темы данной главы — это выделение памяти и доступ к переменным и данным. Мы более детально рассмотрим управление памятью, включая такие вопросы, как распределение стека, управление кучей и сборка мусора. В следующей главе будут рассмотрены методы генерации целевого кода для многих распространенных языковых конструкций. 7.1 Организация памяти С точки зрения разработчика компилятора, целевая программа работает в собственном адресном пространстве, в котором у каждого программного значения есть свое местоположение в памяти. Управление этим логическим адресным пространством и его организация разделяются между компилятором, операционной системой и целевой машиной. Операционная система отображает логические адреса на физические, которые обычно разбросаны в памяти.
Представление времени выполнения объектной программы в пространстве логических адресов состоит из областей данных и программы, как показано на рис. 7.1. Компилятор для языка наподобие С++ в операционной системе типа 1лппх может подразделять память указанным образом. 526 Глава 7. Среды времени выполнения Рис. 7.!. Типичноеразделение памяти времени выполнения на области кода н данных В этой книге мы полагаем, что память времени выполнения имеет вид блоков смежных байтов, где байт — наименьшая адресуемая единица памяти.
Байт состоит из восьми битов, а четыре байта образуют машинное слово. Многобайтные объекты хранятся в последовательных байтах, а их адреса определяются адресами их первых байтов. Как говорилось в главе 6, количество памяти, необходимое для конкретного имени, определяется его типом. Элементарные типы данных, такие как символы, целые и действительные числа, могут храниться в целом количестве байтов. Память, выделенная для составных типов, таких как массивы или структуры, должна быть достаточно велика для хранения всех их компонентов. На схему размещения объектов данных в памяти сильное влияние оказывают ограничения адресации на целевой машине. На многих машинах команды сложения целых чисел могут требовать выравнивания (а11яп) целых чисел, т.е. их размещения в адресах, кратных 4.
Хотя массив из десяти символов требует только десяти байтов для хранения своего содержимого', компилятор может выделить ему для достижения корректного выравнивания 12 байт, оставляя 2 байта неиспользуемыми. Память, остающаяся неиспользуемой нз-за выравнивания, известна как заполнение (раг)п)пй). В случаях, когда вопросы зкономии памяти выходят на первое место, компилятор может лаковалзь (раск) данные так, чтобы заполнения не было; однако при этом могут потребоваться дополнительные команды времени выполнения для позиционирования упакованных данных таким образом, чтобы они могли быть обработаны так, как если бы они были корректно выровнены.
Размер сгенерированного целевого кода фиксируется во время компиляции, так что компилятор может разместить выполнимый целевой код в статически В языках, где размер символа — 1 байт. — Прим. лед 527 7.!. Организация памяти определенной области (Код на рис. 7. !), обычно в нижних адресах памяти. Аналогично в процессе компиляции может быть известен размер некоторых объектов данных программы, таких как глобальные константы, и данных, сгенерированных компилятором, таких как информация для поддержки сборки мусора; такие данные также могут быть размещены в другой статически определяемой области (Статические данные на рис. 7.
!). Одна из причин статического выделения памяти для максимально возможного количества обьектов данных заключается в том, что адреса этих обьектов могут быть встроены в целевой код. В ранних версиях гоПгап память для всех объектов данных могла выделяться только статически. Для максимального использования памяти во время выполнения программы две другие области, Стек и Куча, находятся по разные стороны оставшегося адресного пространства.
Это динамические области — их размеры могут изменяться в процессе работы программы, причем области при необходимости растут одна навстречу другой. Стек используется для хранения структур данных, называющихся записями активации и генерируемых при вызовах процедур. На практике стек растет по направлению к меньшим адресам, а куча — по направлению к ббльшим. Однако в этой и следующей главах мы считаем, что стек также растет в направлении больших адресов, что позволяет использовать во всех наших примерах положительные значения смещений. Как будет видно из следующего раздела, для хранения при вызове процедуры информации о состоянии машины, такой как счетчик команд и регистры машины, используется запись активации.
Когда управление возвращается из вызова, активация вызывающей процедуры может быть возобновлена после восстановления значений регистров и установки счетчика команд в точку, следующую непосредственно за вызовом. Объекты данных, время жизни которых находится в пределах времени жизни активации, могут располагаться в стеке вместе с другой информацией, связанной с активацией.