- •Глава 14 Генерация
- •1 Примером контекстной зависимости, часто встречающейся во многих языках программирования, может служить необходимость предварительно описать идентификатор до его первого использования.
- •Этапы семантического анализа
- •Идентификация лексических единиц языков программирования
- •Распределение памяти. Принципы распределения памяти
- •1 Некоторые вычислительные системы даже вообще не могут работать с адресами, не кратными определенному числу байт.
- •Глобальная и локальная память
- •Статическая и динамическая память
- •Дисплей памяти процедуры (функции). Стековая организация дисплея памяти
- •Стековая организация дисплея памяти процедуры (функции)
- •Память для типов данных (rtti-информация)
- •Способы внутреннего представления программ
- •Синтаксические деревья
- •Многоадресный код с явно именуемым результатом (тетрады)
- •Многоадресный код с неявно именуемым результатом (триады)
- •Обратная польская запись операций
- •Вычисление выражений с помощью обратной польской записи
- •Схемы су-перевода
- •Пример схемы су-перевода дерева операций на язык ассемблера
- •Пример схемы су-перевода дерева операций в последовательность триад
- •Оптимизация линейных участков программы Принципы оптимизации линейных участков
- •Свертка объектного кода
- •Исключение лишних операций
- •Другие методы оптимизации программ Оптимизация вычисления логических выражений
- •Оптимизация передачи параметров в процедуры и функции
- •Оптимизация циклов
- •1 Незначительный выигрыш в скорости достигается за счет исключения всех операций с индуктивной переменной, однако объем программы может существенно возрасти.
- •Машинно-зависимые методы оптимизации
- •Распределение регистров процессора
- •Оптимизация кода для процессоров, допускающих распараллеливание вычислений
Дисплей памяти процедуры (функции). Стековая организация дисплея памяти
Дисплей памяти процедуры (функции) — это область данных, доступных для обработки в этой процедуре (функции).
Как правило, дисплей памяти процедуры включает следующие составляющие:
--- глобальные данные (переменные и константы) всей программы;
а формальные аргументы процедуры;
--- локальные данные (переменные и константы) данной процедуры.
1 B принципе в этом случае разработчик исходной программы может даже и не подозревать о том, что используемая им операция предполагает перераспределение памяти. Однако это не лучшим образом будет сказываться на эффективности созданной разработчиком программы.
C глобальными данными никаких проблем не возникает — их адреса известны, они все устанавливаются на этапе распределения памяти. Даже если память под эти данные распределяется динамически, компилятору однозначно известно, как к этой памяти обратиться, и способ обращения одинаков для всей программы. Поэтому он всегда может создать код для обращения к этим данным вне зависимости от того, откуда это обращение происходит.
Сложнее с локальными параметрами и переменными процедуры. Они относятся к локальной памяти процедуры и потому должны быть как-то связаны с нею. Существует несколько вариантов связывания локальных переменных и параметров с кодом соответствующей процедуры или функции. Им соответствуют варианты организации дисплея памяти процедуры, перечисленные ниже:
а статическая организация дисплея памяти процедуры;
а динамическая организация дисплея памяти процедуры;
--- стековая организация дисплея памяти процедуры.
Статическая организация дисплея памяти процедуры (функции) предусматривает, что с каждой процедурой (функцией) исходной программы при распределении памяти компилятор связывает фиксированную область памяти, предназначенную для размещения ее локальных данных (переменных и параметров). Адрес этой области памяти фиксируется на этапе распределения памяти. Каждому формальному параметру процедуры (функции) и каждой локальной переменной соответствует своя группа ячеек в этой области памяти. Отдельная группа ячеек отводится для хранения адреса возврата — адреса, по которому должно быть передано управление по завершению выполнения процедуры (функции).
Тогда в каждой точке вызова процедуры компилятор порождает код, который размещает фактические параметры вызова процедуры (функции) в ячейках области памяти процедуры, соответствующих ее формальным параметрам. Перед вызовом процедуры запоминается адрес возврата, который должен указывать на фрагмент кода непосредственно за местом вызова, после чего управление передается самой процедуре. B коде процедуры обращение к локальным данным (параметрам и переменным) происходит по фиксированным адресам, которые для каждой процедуры известны после этапа распределения памяти. После завершения выполнения процедуры (функции) из фиксированной ячейки памяти извлекается код возврата и происходит передача управления по нему.
Схема статической организации памяти проиллюстрирована на рис. 14.1.
Данная схема мало чем отличается от обычного распределения памяти под статические глобальные данные, за исключением того, что области памяти процедур, которые никогда в тексте программы не вызывают друг друга, могут частично или полностью перекрываться. Эта схема проста и удобна, она не требует сложной адресации. За счет использования одной и той же области памяти для
B принципе в этом случае разработчик исходной программы может даже и не подозревать о том, что используемая им операция предполагает перераспределение памяти. Однако это не лучшим образом будет сказываться на эффективности созданной разработчиком программы.
хранения локальных данных процедур и функций, которые в исходной программе никогда не вызывают друг друга, компилятор может добиться достаточно эффективного распределения памяти в результирующей программе. У этой схемы есть один серьезный недостаток — она не допускает рекурсивных вызовов процедур и функций.
Действительно, если в схеме, приведенной на рис. 14.1, в момент выполнения кода процедуры A вновь выполнить рекурсивный вызов той же самой процедуры, то все ее локальные данные будут записаны заново (в том числе и код возврата!). После возврата из вызванной процедуры данные вызвавшей процедуры будут безвозвратно потеряны. Процедура, рекурсивно вызвавшая сама себя, в такой схеме просто не сможет корректно завершиться. To же самое произойдет, если рекурсивный вызов будет происходить не непосредственно, а через цепочку вызовов других процедур.
Этот серьезный недостаток ограничивает применение данной схемы организации дисплея памяти процедур и функций в современных компиляторах1.
1 Тем не менее такую простую и эффективную схему организации дисплея памяти процедуры (функции) можно встретить, например, в некоторых старых версиях компиляторов с языков FORTRAN и Basic (эти языки не допускают рекурсивных вызовов процедур, а потому в них возможно использование данной схемы).
Динамическая организация дисплея памяти процедуры (функции) основана на тех же принципах, что и статическая, но память для локальных данных процедуры выделяется в ней динамически всякий раз в момент вызова, а по завершении процедуры — освобождается. Адрес области памяти, связанной с каждой процедурой, в данном случае уже не может быть фиксированным — его надо каким-либо образом передать в процедуру (функцию). Для этой цели используется один из регистров процессора, называемый регистром адресации. Тогда при вызове процедуры необходимо выполнить следующие действия:
--- выделить область памяти, необходимую для хранения локальных данных процедуры, и запомнить ее адрес;
--- заполнить значения параметров процедуры и адрес возврата; S Q запомнить состояние регистра адресации в точке вызова;
--- поместить в регистр адресации адрес области памяти процедуры и передать управление процедуре.
После выполнения этих действий может выполняться код вызванной процедуры. Доступ ко всем локальным данным и параметрам в нем осуществляется только ! через регистр адресации.
При возврате из процедуры необходимо выполнить следующие действия:
--- выбрать из области памяти процедуры адрес возврата (сразу за точкой вызова) и передать туда управление;
--- освободить область памяти, на которую указывает регистр адресации;
--- по смещению относительно точки возврата выбрать сохраненное значение и поместить его в регистр адресации.
После этого можно продолжить выполнение кода результирующей программы, следующего за вызовом процедуры.
Данная схема, хотя и допускает рекурсивные вызовы, не получила широкого распространения. Это вызвано целым рядом причин. Во-первых, она требует постоянного динамического перераспределения памяти — это достаточно сложная операция, которая замедляет выполнение вызовов. Во-вторых, такая схема предполагает достаточно сложную схему адресации локальных данных. Наконец, она требует хранения промежуточных данных непосредственно в исполняемом коде программы, что представляет определенную сложность.
: B настоящее время эта схема не используется, поскольку уступает почти по всем ! показателям схеме стековой организации дисплея памяти.
Стековая организация дисплея памяти процедуры (функции) основана на том, что в момент вызова все параметры процедуры и адрес возврата помещаются в единый для всей результирующей программы стек параметров, а при завершении выполнения программы они извлекаются («выталкиваются») из стека. Код процедуры адресуется к локальным данным по смещениям относительно верхушки стека.
Эта схема получила наибольшее распространение в современных компиляторах, поэтому далее она рассмотрена наиболее подробно.
