
- •Теория вычислительных процессов и структур
- •1. Предварительные математические сведения
- •1.2. Операции над множествами Объединение множеств
- •Пересечение множеств
- •Разность множеств
- •1.3. Множества цепочек
- •1.4. Языки
- •1.5. Алгоритмы
- •1.6. Некоторые понятия теории графов
- •2. Введение в компиляцию
- •2.1. Задание языков программирования
- •2.2. Синтаксис и семантика
- •2.3. Процесс компиляции
- •2.4. Лексический анализ
- •2.5. Работа с таблицами
- •2.6. Синтаксический анализ
- •2.7. Генератор кода
- •Алгоритм.
- •2.8. Оптимизация кода
- •2.9. Исправление ошибок
- •2.10. Резюме
- •3. Теория языков
- •3.1. Способы определения языков
- •3.2. Грамматики
- •Пример.
- •3.3. Грамматики с ограничениями на правила
- •3.4. Распознаватели
- •3.5. Регулярные множества, их распознавание
- •3.6. Регулярные множества и конечные автоматы
- •3.7. Графическое представление конечных автоматов
- •3.8. Конечные автоматы и регулярные множества
- •3.9. Минимизация конечных автоматов
- •3.10. Контекстно-свободные языки
- •3.10.1. Деревья выводов
- •3.10.2. Преобразование кс–грамматик
- •3.10.3. Грамматика без циклов
- •3.10.4. Нормальная форма Хомского
- •3.10.5. Нормальная формула Грейбах
- •3.11. Автоматы с магазинной памятью
- •3.11.1. Основные определения
- •3.11.2. Эквивалентность мп-автоматов и кс-грамматик
- •4.1. Эквивалентность мп-автоматов и кс-грамматик
- •4.2. Ll(1)-грамматики
- •4.3. Ll(1)-таблица разбора
- •5. Синтаксический анализ снизу вверх
- •5.1. Разбор снизу вверх
- •5.2. Lr(1) - таблица разбора
- •5.3. Построение lr – таблицы разбора
- •5.4. Сравнение ll – и lr – методов разбора
- •6. Включение действий в синтаксис
- •6.1. Получение четверок
- •6.2. Работа с таблицей символов
- •7. Проектирование компиляторов
- •7.1. Число проходов
- •7.2. Таблицы символов
- •Identifier, type.
- •Int procedure rehash(int n)
- •Int procedure rehash(int n)
- •7.3. Таблица видов
- •8. Распределение памяти
- •8.1. Стек времени прогона
- •Integer a, b, X, y
- •Int table[1:10, -5:5].
- •8.2. Методы вызова параметров
- •8.3. Обстановка выполнения процедур
- •8.4. «Куча»
- •8.5. Счетчик ссылок
- •8.6. Сборка мусора
- •9. Генерация кода
- •(Тип – адреса, номер - блока, смещение).
- •9.2. Структура данных для генерации кода
- •9.3. Генерация кода для типичных конструкций
- •9.3.1. Присвоение
- •9.3.2. Условные зависимости
- •If b then c else d
- •9.3.3. Описание идентификаторов
- •9.3.4. Циклы
- •9.3.5. Вход и выход из блока
- •9.3.6. Прикладные реализации
- •9.4. Проблемы, связанные с типами
- •9.5. Время компиляции и время прогона
- •10. Исправление и диагностика ошибок
- •10.1. Типы ошибок
- •10.2. Лексические ошибки
- •10.3. Ошибки в употреблении скобок
- •Begin end
- •Case esac
- •10.4. Синтаксические ошибки
- •10.5. Методы исправления синтаксических ошибок
- •End begin
- •10.6. Предупреждения
- •10.7. Сообщения о синтаксических ошибках
- •10.8. Контекстно-зависимые ошибки
- •Identifier xyz not declared
- •Identifier blank alredy declared in block
- •10.9. Ошибки, связанные с употреблением типов
- •Int I; char c;
- •10.10. Ошибки, допускаемые во время прогона
- •10.11. Ошибки, связанные с нарушением ограничений
8. Распределение памяти
8.1. Стек времени прогона
После выяснения структуры программы необходимо выделить место в памяти для внесения значений переменных и поместить соответствующие адреса в таблицу символов. Фаза распределения памяти практически не зависит от языка и машины и одинакова для большинства языков, имеющих блочную структуру. Распределение памяти заключается в отображении значений, появляющихся в программе, на запоминающее устройство машины. Если реализуемый язык имеет блочную структуру, а ЭВМ имеет линейную память, то наиболее подходящим устройством, на котором будет базироваться распределение памяти, является стек или память магазинного типа.
Каждой программе для хранения значений переменных и промежуточный значений выражений необходим определенный объем памяти. Например, если идентификатор описывается как
int x,
т.е. хможет принимать значение типа целого, то компилятору необходимо выделить память длях. Иными словами, компилятор должен выделить достаточно места, чтобы записать любое целое число. Аналогично, еслиу описывается как
struct (int number, real size, bool n) y,
то компилятор обеспечивает для значения упамять с объемом, достаточным для хранения в нем целого, вещественного и логического значения. В обоих случаях компилятор не должен испытывать затруднений в вычислении требуемого объема памяти. Еслиzописывается
int z[10],
то объем памяти, необходимый для хранения всех элементов z, в 10 раз больше памяти для записи одного целого значения. Однако, если быwбыл описан в виде
int w[n],
а значение nоказалось бы неизвестным во время компиляции (оно должно быть рассчитано программой), то компилятор не знал бы, какой объем памяти ему нужно выделить дляw. Обычноw называютдинамическиммассивом. Память дляw выделяется во время прогона. Память, выделяемую во время компиляции, называютстатической, а во время прогона –динамической. В большинстве компиляторов память для массивов (даже имеющих ограничения констант) выделяется во время прогона, поэтому она считается динамической.
Память нужна также для промежуточных результатов и констант. Например, при вычислении выражения a + c d сначала вычисляетсяc d, причем значение запоминается в машине, а затем выполняется сложение. Память, используемая для хранения результатов, называетсярабочей. Рабочая память может быть статической и динамической.
В каждом компиляторе предусмотрена схема распределения памяти, которая до некоторой степени зависит от компилируемого языка. В Фортране память, выделяемая для значений идентификаторов, никогда не освобождается, так что подходящей структурой является одномерный массив. Если считать, что массив имеет левую и правую стороны, память может выделяться слева направо. При этом применяется указатель, показывающий первый свободный элемент массива. Например, в результате описания
Integer a, b, X, y
выделяется память, как это показано на рис. 8.1.
|
B |
X |
Y |
|
|
|
|
|
|
Рис. 8.1. Распределение памяти для Фортрана
Такая схема не учитывает тот факт, что рабочая память используется неоднократно и весьма неэффективна для языка с блочной структурой.
В языке, имеющем блочную структуру, память обычно высвобождается при выходе из блока, которому она выделена. В этом случае оптимальным решением было бы разрешить указателю отодвигаться влево при высвобождении памяти. Такой механизм распределения эквивалентен стеку времени прогона или памяти магазинного типа.
Пусть имеется программа вида:
begin real x, y
.
.
.
begin int c, d
.
.
.
end
begin char p, q
.
.
.
end
.
.
.
end
На рис. 8.2 показаны «моментальные снимки» стека времени прогона на различных этапах ее выполнения.
|
|
|
|
|
|
|
d |
|
q |
|
|
c |
|
p |
y |
|
y |
|
y |
x |
|
x |
|
x |
(1) |
|
(2) |
|
(3) |
Рис. 8.2. Стек времени прогона
На рис. 8.2 изображено место, занимаемое значениями идентификаторов во время прогона. Часть стека, соответствующую определенному блоку, называют рамкойстека.Указатель стекапоказывает на его первый свободный элемент.
Кроме указателя стека требуется также указатель на дно текущей рамки (указатель рамки). При входе в блок этот указатель устанавливается равным текущему значению указателя стека. При выходе из блока сначала указатель стека устанавливается равным значению, соответствующему включающему блоку. Указатель рамки включающего блока может храниться в нижней части текущей рамки стека, образуя часть статической цепи или массива, который называетсядисплеем. Его можно использовать для хранения во время прогона указателей на начало рамок стека, соответствующих всем текущим блокам (рис. 8.3).
|
|
|
|
|
|
|
|
q |
|
|
|
|
p |
|
|
|
|
x |
|
ДИСПЛЕЙ |
|
|
y |
СТЕК |
Рис. 8.3. Система «дисплей-стек»
Это упрощает настройку указателя при выходе из блока.
Если бы вся память была статической, адреса времени прогона могли бы распределяться во время компиляции, и значения элементов дисплея также были бы известны во время компиляции.
Рассмотрим пример программы:
begin int n; read(n);
int numbers[n];
real p;
begin real x, y;
Место для numbers должно выделяться в первой рамке стека, а длях иу– в рамке над ней. Но во время компиляции неизвестно, где должна начинаться вторая рамка, так как неизвестен размер чисел. Одно из решений в этой ситуации – иметь два стека: один для статической памяти, распределяемой в процессе компиляции, а другой для динамической памяти, распределяемой в процессе прогона.
Другое решение заключается в том, чтобы при компиляции выделять статическую память в каждом блоке в начале каждой рамки, а при прогоне – динамическую память над статической в каждой рамке. Это значит, что когда происходит компиляция, неизвестно, где начинаются рамки, но можно распределить статические адреса относительно начала определенной рамки. При прогоне точный размер рамок, соответствующих включающим блокам, известен, так что при входе в блок нужный элемент дисплея всегда можно установить так, чтобы он указывал на начало новой рамки (рис. 8.4).
|
|
|
|
|
|
Рамка 2 |
|
|
|
|
|
| |
|
|
у |
|
|
| |
|
|
х |
|
|
| |
|
|
Числа
|
|
Динамическая часть |
|
Рамка 1 |
|
|
p |
|
Статическая часть |
| |
|
|
n |
| |||
Дисплей |
Стек |
|
|
|
|
Рис. 8.4. Стек прогона для массива
В этой структуре массив занимает только динамическую память. Однако некоторая информация о массиве известна во время компиляции, например его размерность (а, следовательно, и число границ – две на каждую размерность), и при выборке определенного элемента массива она может потребоваться. В некоторых языках сами границы могут быть неизвестны при компиляции, но всегда известно их число, и для значений этих границ можно выделить статическую память. Тогда можно считать, что массив состоит из статической и динамической частей. Статическая часть массива может размещаться в статической части рамки, а динамическая – в динамической. Кроме информации о границах, в статической части может храниться указатель на сами элементы массива (рис. 8.5).
|
|
|
|
|
|
Рамка 2 | |
|
|
|
|
|
| ||
|
|
у |
|
|
| ||
|
|
х |
|
|
| ||
|
|
Элементы чисел
|
|
Динамическая часть |
|
Рамка 1 | |
|
p |
|
Статиче |
| |||
|
|
Статическая часть чисел |
|
ская часть |
|
| |
|
|
n |
|
|
|
| |
Дисплей |
Стек |
|
|
|
|
Рис. 8.5. Модифицированный стек прогона для массива
Когда в программе выбирается конкретный элемент массива, его адрес внутри динамической памяти должен вычисляться в процессе прогона. Пусть имеется массив