- •21. Нисходящий и восходящий грамматический разбор.
- •22. Общая схема работы компилятора.
- •23. Компилятор, транслятор, интерпретатор, блаблабла.
- •24. Фазы компиляции.
- •25. Фаза лексического анализа.
- •26. Таблицы Идентификаторов.
- •27. Способы организации таблиц идентификаторов.
- •29. Методы сортировки.
- •30. Фаза синтаксического анализа.
- •31. Обработка синтаксических ошибок. Виды ошибок.
- •32. Стратегии восстановления после ошибок.
- •33. Способы внутреннего представления входной программы.
- •34. Фаза семантического анализа.
- •35. Фаза генерации кода.
- •36. Виды переменных.??? Классификация областей памяти.
- •37. Фаза генерации кода. Основные принципы.
- •38. Фаза оптимизации кода.
36. Виды переменных.??? Классификация областей памяти.
Глобальная и локальная память
Глобальная область памяти — это область памяти, которая выделяется один раз при инициализации результирующей программы и действует все время выполнения результирующей программы.
Как правило, глобальная область памяти может быть доступна из любой части исходной программы, но многие языки программирования позволяют налагать синтаксические и семантические ограничения на доступность даже для глобальных областей памяти. При этом сами области памяти и связанные с ними лексические единицы остаются глобальными, ограничения налагаются только на возможность их использования в тексте исходной программы (и эти ограничения, в принципе, возможно обойти).
Локальная область памяти — это область памяти, которая выделяется в начале выполнения некоторого фрагмента результирующей программы (блока, функции, процедуры или оператора) и может быть освобождена по завершении выполнения данного фрагмента.
Доступ к локальной области памяти всегда запрещен за пределами того фрагмента программы, в котором она выделяется. Это определяется как синтаксическими и семантическими правилами языка, так и кодом результирующей программы. Даже если удастся обойти ограничения, налагаемые входным языком, использование таких областей памяти вне их области видимости приведет к катастрофическим последствиям для результирующей программы. Распределение памяти на локальные и глобальные области целиком определяется семантикой языка исходной программы. Только зная смысл синтаксических конструкций исходного языка, можно четко сказать, какая из них будет отнесена в глобальную область памяти, а какая — в локальную. Иногда в исходном языке для некоторых конструкций нет четкого разграничения, тогда решение об их отнесении в ту или иную область памяти принимается разработчиками компилятора и может зависеть от используемой версии компилятора. При этом разработчики исходной программы не должны полагаться на тот факт, что один раз принятое решение будет неизменным во всех версиях компилятора. Рассмотрим для примера фрагмент текста модуля программы на языке Pascal:
const
Global_1 = 1;
Global_2 : integer = 2;
var
Global_I : integer;
…
function Test (Param: integer): pointer;
const
Local_1 = 1;
Local_2 : integer = 2;
var
Local_l : integer;
Begin
…
End;
Согласно семантике языка Pascal, переменная Global_I является глобальной переменной языка и размещается в глобальной области памяти, константа Global_1 также является глобальной, но язык не требует, чтобы компилятор обязательно размещал ее в памяти — значение константы может быть непосредственно пoдставлено в код результирующей программы там, где она используется. Типизированная константа Global_2 является глобальной, но, в отличие от констант; Global_l, семантика языка предполагает, что эта константа обязательно будет размещена в памяти, а не в коде программы. Доступность идентификатор: Globa1_I, Global_2 и G1oba1_l из других модулей зависит от того, где и как описаны. Например, для компилятора Borland Pascal переменные и константы, описанные в заголовке модулей, доступны из других модулей программы, а переменные и константы, описанные в теле модулей, недоступны, хотя и те и другие являются глобальными.
Параметр Раram функции Test, переменная Local_I и константа Local_1 являются локальными элементами этой функции. Они не доступны вне пределов данной функции и располагаются в локальной области памяти. Типизированная константа Local_2 представляет собой очень интересный элемент программы. С одной стороны, она не доступна вне пределов функции Test и потому является с локальным элементом. С другой стороны, как типизированная константа она будет располагаться в глобальной области памяти, хотя ниоткуда извне не может быть доступна (аналогичными конструкциями языка С, например, являются локальные переменные функций, описанные как static).
Семантические особенности языка должны учитывать создатели компилятора, когда разрабатывают модуль распределения памяти. Безусловно, это должен знать и разработчик исходной программы, чтобы не допускать семантически, (смысловых) ошибок — этот тип ошибок сложнее всего поддается обнаружение. Так, в приведенном выше примере в качестве результата функции Test может выступать адрес переменной Global_I или типизированной константы Global_2. Результатом функции не может быть адрес констант Global_l или Local_1 — это запрещено семантикой языка. Гораздо сложнее вопрос с переменной Local_I или параметром функции Param. Будучи элементами локальной области памяти функции Test, они никак не могут выступать в качестве результата функции, потом; что он будет использован вне самой функции, когда область ее локальной памяти может уже не существовать. Но ни синтаксис, ни семантика языка не запрещают использовать адрес этих элементов в качестве результата функции (и далеко не всегда компилятор способен хотя бы обнаружить факт такого использования адреса локальной переменной). Эти особенности языка должен учитывать разработчик программы. А вот адрес типизированной константы Loca1_2, в принципе может быть результатом функции Test. Ведь хотя она и является локальной константой, но размещается в глобальной области памяти (другое дело, что этим пользоваться не рекомендуется, поскольку нет гарантии, что принцип размещения локальных типизированных констант не изменится с переходом к другой версии компилятора).
Статическая и динамическая память
Статическая область памяти — это область памяти, размер которой известен на этапе компиляции.
Поскольку для статической области памяти известен ее размер, компилятор всегда может выделить эту область памяти и связать ее с соответствующим элементом программы. Поэтому для статической области памяти компилятор порождает некоторый адрес.
Статические области памяти обрабатываются компилятором самым простейшим образом, поскольку напрямую связаны со своим адресом. В этом случае говорят о статическом связывании области памяти и лексической единицы входного языка. Динамическая область памяти — это область памяти, размер которой на этапе компиляции программы не известен.
Размер динамической области памяти будет известен только в процессе выполнения результирующей программы. Поэтому для динамической области памяти компилятор не может непосредственно выделить адрес — для нее он порождает фрагмент кода, который отвечает за распределение памяти (ее выделение и освобождение). Как правило, с динамическими областями памяти связаны многие операции с указателями и с экземплярами объектов (классов) в объектно-ориентированных языках программирования. При использовании динамических областей памяти говорят о динамическом связывании области памяти и лексической единицы входного языка.
Динамические области памяти, в свою очередь, можно разделить на динамические области памяти, выделяемые пользователем, и динамические области памяти, выделяемые непосредственно компилятором.
Динамические области памяти, выделяемые пользователем, появляются в тех случаях, когда разработчик исходной программы явно использует в тексте программы функции, связанные с распределением памяти (примером таких функций являются New и Dispose в языке Pascal, malloc и free в языке С, new и delete в языке C++ и другие). Функции распределения памяти могут использовать либо напрямую средства ОС, либо средства исходного языка (которые, в конце концов, все равно основаны на средствах ОС). В этом случае за своевременное выделение и освобождение памяти отвечает сам разработчик, а не компилятор. Компилятор должен только построить код вызова соответствующих функций и сохранение результата — в принципе, для него работа с динамической памятью пользователя ничем не отличается от работы с любыми другими функциями и дополнительных сложностей не вызывает.
Другое дело — динамические области памяти, выделяемые компилятором. Они появляются тогда, когда пользователь использует типы данных, операции над которыми предполагают перераспределение памяти, не присутствующее в явном виде в тексте исходной программы. Примерами таких типов данных могут служить строки в некоторых языках программирования, динамические массивы и, конечно, многие операции над экземплярами объектов (классов) в объектно-ориентированных языках (наиболее характерный пример — тип данных string в версии Borland Delphi языка Object Pascal). В этом случае сам компилятор отвечает за порождение кода, который будет всегда обеспечивать своевременное выделение памяти под элементы программы и освобождать ее по мере использования. Как статические, так и динамические области памяти сами по себе могут быть глобальными или локальными.
