Вы находитесь на странице: 1из 103

Лекция 1.

Промежуточная форма представления программы.

Построение ПОЛИЗ при восходящем разборе.

В процессе генерации объектного кода первоначальная исходная


программа переводится в некоторую внутреннюю форму, более удобную для
дальнейшей обработки. В большинстве внутренних представлений
операторы располагаются в том порядке, в котором должны выполнятся. Это
облегчает последующую генерацию объектного кода. Одной из внутренних
форм представления является польская инверсная запись.

Рассмотрим польскую запись на примере арифметических выражений.


При трансляции арифметического выражения мы должны получить в
результате просмотра выражения слева направо последовательность
действий с использованием промежуточных результатов. Например
выражение a+(b*c-d) должно быть приведено в последовательность команд:

R1: =a;

R2: =b;

R3: =c;

R2: =R2*R3;

R3: =d;

R2: =R2 - R3;

R1: =R1+R2

Эти команды можно разделить на два типа:

1. команды, заполняющие ячейки промежуточных результатов;

2. команды, выполняющие какую-нибудь операцию над содержимым


двух последних ячеек промежуточных результатов.
Приведенную группу команд можно сократить, удалив все ссылки к
ячейкам промежуточных результатов и переписав ее как последовательность

a, b, c, *, d, -, +.

Такая последовательность может быть использована в качестве команд,


определяющих порядок вычислений с использованием стека (или магазинной
памяти). Любой символ, являющийся операндом, заносится в стек, а по
символу операции выполняется действие над верхними ячейками стека с
занесением результата вместо используемых операндов.

Такой подход положен в основу использования польской инверсной


записи (ПОЛИЗ) или (бесскобочной записи) предложенной польским
математиком Я.Лукашевичем.

Обычная запись арифметических выражений называется инфиксной,


польская инверсная (или польская обратная) называется постфиксной.

В постфиксной записи знак операции следует сразу за ее oпeрандами.


Таким образом, a+b записывается как ab+, а выражения

(a+b)*c a+b*c a*b+c

как

ab+c* abc*+ ab*c+.

Существуют различные модификации ПОЛИЗ и алгоритмы перевода из


инфиксной записи в ПОЛИЗ и обратно. Но одному арифметическому
выражению в инфиксной записи соответствует одно выражение в ПОЛИЗ.

Например, a+b*(c+d)*(e+f) в ПОЛИЗ будет abcd+*ef+*+.

Как правило, компиляторы имеют синтаксический блок, который


переводит инфиксные выражения в ПОЛИЗ или подобную ПОЛИЗ форму
(например, префиксную, когда знак стоит перед операндами, но наиболее
удобной является постфиксная форма, т. е. ПОЛИЗ).

Сформулируем следующие правила, касающиеся польской записи.

1. Идентификаторы в ПОЛИЗ следуют в том же порядке, что и в


инфиксной записи;

2. Операторы в ПОЛИЗ следуют в том порядке, в каком они должны


вычисляться (слева направо, порядок может не совпадать с исходным);

3. Операторы располагаются непосредственно за своими операндами.

Таким образом, можно записать следующие синтаксические правила для


ПОЛИЗ:

<операнд>::= i |<операнд><операнд><операция>

<операция>::= + | - | * | / |…

Одноместный минус можно представить двумя способами: либо


записывать как бинарный оператор, т.е.вместо –b писать 0–b, либо для
унарного минуса ввести новый символ, например @, и использовать еще
одно синтаксическое правило <операнд>::=<операнд>@, тогда a+(-b+c*d)
запишется как ab@cd*++.

Вычисление арифметических выражений.

Выражение в ПОЛИЗ вычисляется с использованием стека в соответствии


со следующими правилами:

1. Если текущий символ ПОЛИЗ является идентификатором или


константой, то его значение заносится в стек и осуществляется переход к
следующему символу.
2. Если текущий символ ПОЛИЗ является двухместным оператором, то он
применяется к двум верхним ячейкам стека, результат заносится во вторую
(нижнюю) ячейку, а первая, т.е. верхняя, освобождается.

3. Если текущий символ - одноместный оператор, то он применяется к


содержимому верхней ячейки стека, которое заменяется полученным
результатом.

Проиллюстрируем вычисление выражения ab@cd*++, что соответствует


исходному выражению (a+(-b+c*d)) (значения в стеке отделены друг от
друга вертикальными черточками).

Стек

ab@cd*++

a b@cd*++

a|b @cd*++

a | -b cd*++

a | -b | c d*++

a | -b | c | d *++

a | -b | c*d ++

a | -b+c*d +

a+(-b+c*d)

Перевод инфиксной записи в ПОЛИЗ

Для перевода могут быть использованы любые алгоритмы


грамматического разбора (например «снизу-вверх»). При построении ПОЛИЗ
на основании восходящего разбора важным является то, что разбор ведется
слева направо, т. е. каждый раз редуцируется самая левая простая фраза.
ПОЛИЗ строится одновременно с редукцией, т. е. с заменой основы
нетерминалом. Из того, что разбор ведется слева направо, делаем следующее
важное допущение: если в основе встретился нетерминал, то часть польской
цепочки, соответствующая этому нетерминалу, уже сгенерирована

Будем считать, что польская цепочка хранится в одномерном массиве


Р (элемент массива P(k) содержит один символ) и что мы имеем доступ к
стеку распознавателя S (элемент S(j)).

Рассмотрим построение ПОЛИЗ на примере арифметического выражения,


в котором допускается использование одноместного минуса.

Припишем каждому правилу грамматики предшествования семантическую


подпрограмму, которая будет срабатывать при свертке по этому правилу

Семантичиские
Правила
подпрограммы

E1::=E нет

E::=E+T1 P(k)= +; k=k+1

E::=E-T1 P(k)= -; k=k+1

E::=T1 нет

E::= -T1 P(k)= @; k=k+1

T1::=T нет

T::=T*F P(k)= *; k=k+1

T::=T/F P(k)= /; k=k+1

T::=F нет

F::=(E1) нет

F::=i P(k)= S(j); k=k+1

Рис. 1.1.
Семантические подпрограммы.

Какие же функции должны выполнять семантические подпрограммы?


Рассмотрим подпрограмму, связанную с правилом E::=E+T1.

К моменту редукции основы E+T1 уже редуцировались подцепочки,


соответствующие E и T1, значит, в ПОЛИЗ уже сгенерировано

... <код Е> <код Т1>

.Правее Т1 код еще не сгенерирован, т.к. разбор ведется слева направо.


Таким образом, все что требуется от семантической подпрограммы – это
занести «+» в ПОЛИЗ. Т.е. семантическая подпрограмма имеет вид

P(l)=’+’; k=k+1;

Рис. 1.2

Рассмотрим подпрограмму для правила F::= i, где i - произвольный


идентификатор или константа. Т.к. в ПОЛИЗ идентификаторы предшествуют
своим операторам и следуют в той же последовательности, что и в исходной
Рис 1.3.

инфиксной записи, то задача семантической подпрограммы перенести i в


массив Р, взяв его из стека (элемент S(j))

P(k)=S(j); k=k+1;

.Семантическая подпрограмма, связанная с правилом F=(E1) ничего не


делает, т.к. в ПОЛИЗ скобок нет, а для E польская запись уже сгенерирована.

Учитывая, что таблица предшествования имеет вид, приведенный на


рис. 1.2, построим ПОЛИЗ для фразы A*(-B+C). Разбор приведен на рис.1.3.
Здесь мы хотели показать общий подход разделения синтаксиса и
семантики, хотя в данном случае многие семантические подпрограммы
ничего не делают. Польская инверсная запись получается как побочный
продукт синтаксического разбора.

Рассмотренный способ перевода арифметических выражений в ПОЛИЗ


основан на грамматике, в которой косвенным образом учтена
последовательность операций. Например, операция * редуцируеться раньше,
чем +.
Лекция 2. Польская инверсная запись. Алгоритм Дикстры.

Алгоритм Дикстры построения ПОЛИЗ.

ПОЛИЗ обладает двумя важными свойствами:

1) описываемые ею действия можно выполнить (или программировать) в


процессе одностороннего безвозвратного просмотра слева направо, т. к. в
момент появления знака операции известно местоположение и вычислены
значения соответствующих операндов;

2) операнды расположены в том же порядке, как в исходной записи,


поэтому перевод в ПОЛИЗ сводится к изменению порядка следования знаков
операций.

Именно эти свойства дают основания использовать ПОЛИЗ в качестве


промежуточного языка. Из первого свойства следует легкий перевод из
ПОЛИЗ в машинные команды. Из второго – простое построение ПОЛИЗ с
помощью стека и приоритетов.

Алгоритм Дикстры основан на использовании приоритетов выполняемых


операций, например для арифметических выражений операции могли бы
иметь следующие приоритеты (↑ - обозначим возведение в степень):

Операция Приоритет

+, - 0

*, / 1

↑ 2

Для пояснения метода Дикстры используеться аналогия с


железнодорожной станцией, имеющей форму Т-образного разъезда с
магазином (стеком) в качестве ветки для выполнения маневра или
переупорядочения.

Выход Вход

Магазин

Алгоритм:

1. Идентификаторы и константы проходят от входа прямо к выходу.


Операции обычно достигают выхода через магазин.

2. Если приоритет ограничителя (операции), находящегося в стеке,


больше или равен приоритету текущего ограничителя, то ограничитель из
стека дается на выход и п.2 повторяется, в противном случае текущий
ограничитель заносится в стек.

3. Если стек пуст, то текущий ограничитель заносится в стек.

4. Если входная цепочка пуста, все операции из стека выталкиваются на


выход признаком конца выражения (например";").

Рассмотрим выражение a+b*c/d .После просмотра трех входных символов


получим:
Т.к. приоритет операции “*” выше приоритета операции “+”,то “*”
заноситься в стек. После прохождения символов “*” и “с” получим:

Т.к. приоритет операции “/” равен приоритету “*”,то “*” выталкивается на


выход, а “/” заноситься в стек. Т.к. приритет “+” меньше приоритета “/” ,
получим:

abc* d
операция приоритет
/ 1
+ 0

Далее переменная d передается на выход, и стек очищаеться, т.к. входная


цепочка исчерпана. Получим abc*d/+.

Операция изменения знака должна выполняться раньше двухместного +


или –, т.е. должна иметь приоритет, как "*" или "/" , и ее надо различать в
начале выражения.

Особенности обработки скобок

Некоторые особенности имеют ограничители "(" и ")". Открывающая


скобка должна иметь самый низкий приоритет, но записываться в стек,
ничего не выталкивая, а закрывающая должна иметь приоритет на 1
больший, чтобы выталкивать все, включая "(" (но не дальше), причем "(" на
выход не передается, а ")" никогда не пишется в стек.

Тогда таблица приоритетов будет иметь вид:

Операция Приоритет

( 0

), +, - 1

*, / 2

↑ 3

Используя этот ряд приоритетов преобразуем выражение -a+b*c↑(d/e)/f :

ПОЛИЗ a @ b c d e / ↑ f /

* +

/ /

( ( ( (

↑ ↑ ↑ ↑ ↑ ↑

* * * * * * * * / /

Стек @@ + + + + + + + + + + + +

Исх.цеп. - a + b * c ↑ ( d / e ) / f ;

Получили: a@bcde/↑*f/+.

Распространение ПОЛИЗ на другие выражения и операторы

Чтобы переводить логические выражения по алгоритму Дикстры


необходимо операции отношения и логические операции тоже внести в
таблицу приоритетов.
Операции отношения. Справа и слева от операции отношения стоят
арифметические выражения. Значит, приоритет операции отношения должен
быть таким, чтобы выталкивать из магазина все ограничители, относящиеся к
арифметическому выражению, которое предшествует операции отношения.
То есть, приоритет должен быть меньше приоритета любой арифметической
операции.

Аналогично логические операции должны выталкивать из стека


операции отношения, которые им предшествуют. Таким образом, приоритет
логических операций должен быть меньше, чем операции отношения.

Операция Приоритет

( 0

) or 1

and 2

not 3

< > = <> ≤ ≥ 4

+ - 5

* / 6

↑ 7

Примеры:

1. a-3>-c*d/e a3-c@d*e/>

2. not a+b >3 and c=0 or (a-q)=1 ab+3>not c0=and aq-1=or

3. a>b or c=d and a+2*b>c-1 ab>cd= a2b*+c1->and or


ПОЛИЗ для операторов

Польскую запись очень просто расширить. Надо только придерживаться


правила, что за операндами должен следовать оператор.

Так, присваивание:

<пер>:=<выр>

в польской записи будет иметь вид:

<пер><выр>:=

Оператор “:=” выполняется не так как операция отношения “=”. После его
выполнения и <пер> и <выр> должны быть исключены из стека, т. к.
оператор “:=”, не имеет результирующего значения в стеке в отличие от
двухместных операций, встречающихся в выражениях. Кроме того, в стеке
должно использоваться не значение <пер>, а ее адрес, т. к. значение <выр>
должно запоминаться по адресу <пер>. Эти особенности должны быть
учтены при формировании и выполнении польской цепочки.

Оператор присваивания a:=b+c в ПОЛИЗ представляется как abc+:=

Из этого вытекают требования к величине приоритета знака


присваивания.

1. приоритет знака := должен быть меньше приоритета знака любой


арифметической и логической операции, т.к. оператор присваивания
выполняется после вычисления выражения, записанного в правой части
оператора присваивания.

2. приоритет знака := должен быть больше приоритета конца оператора (;,


end и else), чтобы знак конца оператора очищал стек.

Если в языке допускается выражение


a:=b:=5

то при наличии одного приоритета оператор будет транслироваться в

a:=b:=5:=

при этом обе команды не могут быть выполнены правильно. Поэтому для
символа := вводят сравнительный и стековый приоритеты. Стековый имеет
знак, находящейся в стеке, сравнительный – символ входной цепочки.
Сравнительный приоритет надо взять большим, приоритета любого другого
знака, чтобы знак := не выталкивал из стека других знаков. Тогда a:=b:=5;
будет транслироваться в

a b 5 :=:=

При этом должны генерироваться две различные команды

a b 5 Пр:=

Приоритет
Операция
стековый сравнительный

( 0

) 1

:= 2 10

or 3

and 4

not 5

<, >, =, <>, ≤,


6

+,- 7

-,/,@ 8

^ 9
При этом генерируются две различных команды: a b 5 Пр :=по команде
Пр «5» присваивается «b», имя которой удаляется из стека, по следующей
команде присвоения «5» присваивается «a» и удаляется всё. Наличие этих 2
приоритетов позволяет правильно выполнять трансляцию, если
используются переменные с индексом (в массивах).
Лекция 3.

ПОЛИЗ операторов условного перехода

До сих пор мы рассматривали выражения и операторы, в которых


порядок действий определяется только старшинством операций и скобками,
и в ходе выполнения программы не меняется.

Однако в языках обычно имеются условные выражения или операторы с


динамически изменяемым порядком действий.

Для того, чтобы записать последовательность операций, порождаемых


такими операторами, необходимо ввести некоторые дополнительные
операции и метки.

Для реализации таких переходов введем следующие операции: БП –


безусловный переход и УПЛ – условный переход по лжи. Команды имеют
следующие операнды:

m БП, где m – метка, на которую осуществляется безусловный переход

ЛВ m УПЛ, где ЛВ. – логическое выражение, m – метка. Если значение


ЛВ – ложь, то осуществляется переход на метку m. Если – истина, то
выполняется оператор, следующий за УПЛ.

нет да
ЛВ
m1:

опер2 опер1

m2:
Рис.3.1
Рассмотрим оператор:

if <ЛВ> then <опер1> else <опер2> ;

Блок-схема работы этого оператора приведена на рис.3.1. Т.к. мы будем


использовать введенную ранее команду УПЛ, то пометим меткой m1
переход по “лжи”, а меткой m2 выход из оператора. Опишем эту блок-схему
в коде ПОЛИЗ. Получим:

ЛВ m1 УПЛ <опер1> m2 БП m1: <опер2> m2:

then else ;

Сравним полученную запись с исходной записью оператора и определим


дополнительную нагрузку на служебные слова исходного оператора и их
приоритеты. Очевидно, по then надо генерировать в ПОЛИЗ фрагмент
m1 УПЛ , по else – фрагмент m2 БП m1: , по символу ; - фрагмент m2: .

Особенности алгоритма перевода оператора условного перехода в


обратную польскую запись.

Ограничители if, then, else играют в некотором смысле роль скобок.

Символ if эквивалентен (, а символы then и else являются закрывающими


скобками для предшествующего выражения и открывающими для
последующего. Поэтому символу if приписывается приоритет 0, а then, else –
приоритет 1.

Особенности обработки отдельных символов:

1. Символ if, имеющий приоритет 0, как и любая открывающая скобка


записывается в вершину стека. Этот символ используется в качестве
«хранителя» и «переносчика» рабочих меток операций УПЛ и БП. При
появлении символа then, запись if превращается в if mi, а появление символа
else превращает if mi в if mi mi+1 , здесь i – номер очередной
рабочей метки.

2. Символ then с приоритетом 1 выталкивает из стека все знаки до


первого if исключительно

При этом генерируется новая метка mi и в выходную строку заносится

mi УПЛ

Затем метка mi заносится в таблицу меток, а в вершину стека


дописывается mi, получается запись

if mi

3. Символ else с приоритетом 1 выталкивает из стека все знаки до первого


if исключительно. В стеке запись:

if mi

В выходную строку добавляется запись

mi+1 БП mi:

Затем метка mi+1 заносится в таблицу меток, а в вершину стека к записи


if mi дописывается метка mi+1получается

if mi mi+1

4. Символ конца условного оператора (например ;, end) выталкивает из


стека все до ближайшего if Это может быть if mi mi+1 (или if mi в случае
конструкции if <выр> then <опер>;). Запись удаляется из стека, а в
выходную строку добавляется mi+1: (или mi: во втором случае).В случае
вложенных условных операторов (if<выр1>then<опер1>else
if<выр2>then<опер2>else if…) в стеке может быть последовательность:
if mi mi+1
if mi-2 mi-1
........
if mi-2k mi-2k+1

По концу оператора вся последовательность удаляется из стека и по


каждой записи в выходную строку добавляется метка, т.е. в выходную строку
добавится запись вида:

mi+1 : mi-1: ...mi-2k+1 :

Особенности обработки if, then, else можно свести в таблицу:

Символ Запись в стек Запись в вых. строку

if if -

then if mi mi УПЛ

else if mi mi+1 mi+1 БП mi:

Последней метки : из
конец очистка стека
каждой записи if... в
оператора включая if mi mi+1
стеке

Пример:

Транслируем в ПОЛИЗ следующий оператор:

if a>b then a:=5 else a:=0;

Процесс трансляции приведен на рис 3.2


В :=
Ы a b > a 5 m2 a 0 :=
Х m1 БП m2
О УПЛ m1 :
Д :
С
Т
> > := := := :=
Е
if if if if if m1 ifm1 ifm1 ifm1 ifm1m2 ifm1m2 ifm1m2 ifm1m2
К
Вх.
if a > b then a := 5 else a := 0 ;
стр

Рис. 3.2

Итак, получили строку в ПОЛИЗ:

a b > m1 УПЛ a 5 := m2 БП m1: a 0 := m2:


123 4 5 67 8 9 10 11 12 13 14

В коде присутствует операция : (m1: , m2:), выполнение которой заключается


в следующем: она определяет значение метки, т.е. заносит в таблицу меток
адрес очередной рабочей команды, которая помечена этой меткой. И сама
операция и ее операнд – метка удаляются из обрабатываемой строки, т.е.
отсутствуют в ПОЛИЗ. В нашем случае таблица меток будет иметь вид:

Имя метки Значение


m1 11
m2 14

Рассмотрим пример с вложенным оператором условного перехода.

Исходный текст оператора:


if a>b then a:=5 else if a<b then a:=10 else a:=0;

Код ПОЛИЗ:

a b > m1 УПЛ a5 := m2 БП m1: a b < m3 УПЛ a 10 := m4 БП m3: a 0 := m4: m2:

Процесс перевода представляен на рис.3.3.

a b > a 5 := a b < a 10 := a 0 :=

m1 m2 m3 m4 m4
:
УПЛ БП УПЛ БП
m2
m1: m3:
:

< := :=

> := if if ifm3 if m3 ifm3m4 ifm3m4

ifm1m2 ifm1m2
if if ifm1 ifm1 ifm1m2 ifm1m2 ifm1m2 ifm1m2 ifm1m2

if a > b then a := 5 else if a < b then a := 10 else a := 0 ;


m1 m2 m3 m4

Рис. 3.3.

Код ПОЛИЗ:

a b > m1 УПЛ a5 := m2 БП m1: a b < m3 УПЛ a 10 := m4 БП m3: a 0 := m4: m2:


Лекция 4

Перевод в ПОЛИЗ условных операторов разных видов

Рассмотрим примеры использования составных операторов в качестве


<опер1> и <опер2> в операторе условного перехода

if <ЛВ> then <опер1> else <опер2> ;

Пример 4.1

if a = b then a:= 0; b := 1 else a := 1 ;

Здесь составной оператор после then не заключен в операторные скобки,

В :=
Ы a b = a 0 := b 1 m2 a 1 :=

Х m1 БП m2

О УПЛ m1 :

Д :

С
Т
= := := :=
Е
if if if m1 ifm1 ifm1 ifm1 if m1 m2 if m1 m2
К

Вх.
if a = b then a := 0 ; b := 1 else a := 1 ;
стр.

поэтому необходимо обеспечить правильную работу символа ; после


оператора а::=0 . Символ ; “видит ” в стеке if и должен выполняться как
символ окончания оператора if, выгружая стек и генерируя соответствующий
фрагмент на выход. Чтобы этого не произошло, надо анализировать
дополнительные признаки, например, что в стеке при if только одна метка.
Получим ПОЛИЗ:
a b = m1 УПЛ a 0 := b 1 := m2 БП m1: a 1 := m2:

Эта проблема исчезает при использовании операторных скобок.

Пример 4.2

if a=b then begin a:=0; goto M end else a:=1;

В a b = a 0 := M БП m2 a 1 :=

m1 БП m2
Ы

УПЛ m1 :
Х
:

С := goto

= begin begin begin begin :=


Т

if if if m1 if m1 if m1 if m1 if m1 if m1 if m1m2 ifm1m2
Е

К
В if a = b then begin a := 0 ; goto M end else a := 1 ;
х

Получим ПОЛИЗ:

a b=m1УПЛ a 0:= M БП m2 БП m1:a1:=m2:

Рассмотрим пример использования составного оператора послe else.

Пример 4.3

Рассмотрим оператор

if a<b then a:=c else begin a:=2; k:=1 end


В a b < a c := a 2 := k 1 :- m2

Ы
:
m1 m2
Х

УПЛ БП

m1

:
С := :=
Т
< := begin begin begin begin
Е
К if m1m2 if m1m2 if m1m2 if m1m2 if m1m2
if if if m1 if m1 if m1m2

Вх if a < b then a := c else begin a := 2 ; k := 1 end ;

Получим ПОЛИЗ

ab<m1 УПЛac:=m2 БПm1:a2:=k1:=m2:

Рассмотрим сокращенный оператор перехода типа

If <лог. выр.> then <опер1>;

Блок схема выполнения оператора следующая:

Нет ЛВ Да

Опер1

m1:
В соответствии с блок-схемой получим ПОЛИЗ
ЛВ m1 УПЛ <опер1> m1:

then ;
Нагрузка на символы if, then,; такая же, как и в полном операторе
условного перехода.
Рассмотрим пример: if a>b then a:=0;
Его реализация следущая
В a b > a 0 :=
Ы m1 m1
Х УПЛ :

С
Т > :=
Е if if ifm1 ifm1
К
Вх.цеп if a > b then a := 0 ;

Получим ПОЛИЗ a b> m1 УПЛ а 0:=m1:


В языках программирования иногда встречаются операторы вида:
If <лог.выр.> then goto M;
Его блок-схема является частным случаем предыдущего оператора

ЛВ
Нет Да

goto M

m1:
и дает следующий ПОЛИЗ:
ЛВ m1 УПЛ М БП m1:

Then ;
То есть нагрузка на символы if, then,; сохраняется.
Пример
if a>b then goto M;
Реализация:
В a b > M БП
Ы m1 m1
Х УПЛ :
С
Т > goto
Е if if ifm1 ifm1
К
Вх.цеп if a > b then goto M ;

Операция goto при выталкивании из стека заменяется на БП.


Встречаются языковые конструкции , когда вообще отсутствует
служебное слово then , то есть оператор имеет вид:
if <лог.выр.> goto M;
В этом случае задачу организации условного перехода (т.е.
формирования команды УПЛ) можно возложить на goto при условии, что в
стеке if.
Пример реализации:
if a>b goto M;
ПОЛИЗ: a b> m1 УПЛ M БП m1:

goto ;
В a b > M БП
Ы m1 m1
Х УПЛ :
С
Т > goto
Е if if ifm1
К
Вх.цеп if a > b goto M ;

Символ goto, видя в стеке if действует, как then, затем записывается в стек.
При выталкивании из стека goto заменяется на БП.
Реализацию последнего оператора можно модернизировать следующим
образом. Переход на метку М выполняется, если логическое выражение
«истина ». Команда УПЛ работает по «лжи», поэтому достаточно
сгенерировать отрицание логического выражения и метка m1 вообще не
понадобиться.
Итак, будем реализовать оператор
if <лог.выр.> goto M;
следующим образом:
Его блок-схема является частным случаем предыдущего оператора

Нет ¬<лог.в.> Да

goto M
То есть ПОЛИЗ будет:
<лог.выр.> ¬ M УПЛ

Реализация следующая
В a b > M УПЛ
Ы ¬
Х
С >
Т ¬ ¬
Е if if goto
К
Вх.цеп if a > b goto M ;

По if в стеек записывается знак отрицания ¬ (not) и символ if, goto


выталкивает все, включая if; символ ; выталкивая goto, заменяет его на УПЛ.
Получим:
a b > ¬ M УПЛ
Лекция 5
Перевод в ПОЛИЗ операторов цикла
Существуют различные конструкции операторов цикла, которые можно
свести к двум основным типам:
- тип пересчета for l:=a while b do A ;
- тип арифметической прогрессии for l:=a step h to c do A ;
где l - имя переменной параметра цикла,
b- логическое выражение,
a,h,c - арифметические выражения, определяющие начальное значение
параметров цикла, шаг изменения параметра цикла и его
конечное значение соответственно,
A- тело цикла.

Для трансляции в ПОЛИЗ будем использовать команды условного и


безусловного перехода БП и УПЛ. Для разработки алгоритма перевода
конкретного вида оператора рассмотрим его блок-схему.

Рассмотрим оператор пересчета следующего вида for l:=a while b do A;


Блок-схема работы этого оператора следующая:

mi:

l:=a

нет да
b

mi+1:
Очевидно, для реализации потребуется две метки mi и mi+1.
Запишем реализацию полученной блок-схемы в ПОЛИЗ
mi: l a := b mi+1 УПЛ A mi БП mi+1:

for do ;
Сравнивая полученный код с исходным текстом оператора, определяем
дополнительные функции служебных слов при построении ПОЛИЗ и их
приоритеты
- for - приоритет 0; генерирует очередную рабочую метку mi , заносит в
стек запись for mi , заносит в ПОЛИЗ mi: . Символ for играет роль
“хранителя” и “переносчика” рабочих меток, как и символ if в операторах
условного перехода.
- while - приоритет 1 , генерирует очередную рабочую метку mi+1 ,
изменяет в стеке запись for mi на for mi mi+1 , в ПОЛИЗ ничего дополнительно
не заносит.
- do - приоритет 1 , заносит в ПОЛИЗ mi+1 УПЛ.
- ; - приоритет 1 , заносит в ПОЛИЗ mi БП mi+1: и удаляет запись for из
стека.
Все служебные слова и ; действует сначала по своим приоритетам,
выталкивая из стека все до записи for mimi+1 исключительно. А затем,
используя метки, указанные при for, генерирует в ПОЛИЗ соответствующие
фрагменты.
Рассмотрим пример трансляции оператора в ПОЛИЗ.
П m1 l a := c d > c c l +
О : m2 :=
Л УПЛ m1
И БП

З m2
С :
Т +
Е := > := :=
К for m1 for m1 for m1 m2 for m1 m2 for m1 m2 for m1 m2 for m1 m2 for m1 m2

Вх. for l := a while c > d do c := c + l ;


цеп

Получим ПОЛИЗ
m1 : l a := c d > m2 УПЛ c c l + := m1 БП m2:
обеспечивающую правильное выполнение оператора.
Рассмотрим другие виды операторов цикла типа пересчета. Оператор с
предусловием
While b do A ;
Блок схема следующая:

mi:
нет да
b

mi+1:

Используя те же команды условного и безусловного перехода УПЛ и БП,


запишем этот алгоритм в ПОЛИЗ:
mi: b mi+1 УПЛ A mi БП mi+1:

while do ;
Сравним полученный текст с исходным и определим приоритеты и
функциональную нагрузку служебных слов и знаков.
- while - приоритет 0; генерирует рабочую метку mi; записывает в стек
while mi; генерирует в ПОЛИЗ mi: . Выполняет роль хранителя и переносчика
меток в стеке.
- do - приоритет 1; очищает стек до while исключительно; генерирует
рабочую метку mi+1 ; заносит в ПОЛИЗ mi+1 УПЛ.
- ; - приоритет 1 ; очищает стек до while ; генерирует в ПОЛИЗ mi БП
mi+1: и удаляет запись while из стека.
Пример трансляции.

П m1 a b > a a k -
О : m2 :=
Л УПЛ m1
И БП

З m2
:
С
Т
Е -
К > := :=
while m1 while m1 while m1 m2 while m1 m2 while m1 m2

Вх. while a > b do a := a - k ;


цеп

Получим ПОЛИЗ:
m1 : a b > m2 УПЛ a a k - := m1 БП m2:
Оператор цикла с постусловием:
repeat A until b ;
Блок-схема оператора следующая:

mi:

нет да

Запишем этот алгоритм в ПОЛИЗ:


mi: A b mi УПЛ

repeat ;

Сравнивая полученный текст с исходным, определим функциональную


нагрузку и приоритеты служебных слов и символов.
- repeat - приоритет 0; генерирует рабочую метку mi, записывает в стек
repeat mi, генерирует в ПОЛИЗ mi: . Запись в стеке используется для
хранения и переноса метки mi;
- until - приоритет 1; очищает стек до repeat исключительно;
- ; - приоритет 1; очищает стек до repeat; генерирует в ПОЛИЗ mi УПЛ
и удаляет запись repeat из стека.
Пример трансляции.
П m1 a a 1 + a b >
О : := m1
Л УПЛ

И
З

С
Т
Е +
К := := >
repeat m1 repeat m1 repeat m1 repeat m1 repeat m1
Вх. repeat a := a + 1 until a > b ;
цеп

Получим ПОЛИЗ:
m1 : a a 1 + := a b > m1 УПЛ .
Лекция 6

Перевод в ПОЛИЗ операторов цикла типа арифметической прогрессии

Рассмотрим оператор вида for l:=a step h to c do A ;


Где a,h,c- арифметическое выражение; l- имя оператора цикла.
Пусть блок-схема выполнения этого оператора имеет вид:
l:=a

rj :=1
m
i:
rj+1 :=h

не д
rj = 0 а
т

l:=l+rj+
mi 1
+1:

rj :=0

нет д
(l-c) а A
rj+1≤0

mi+2:

Кроме рабочих меток mj, mj+1, mj+2 нам понадобилось использовать еще
две дополнительные рабочие ячейки rj и rj+1.
В rj хранится признак того, что цикл выполняется первый раз и значения
параметра цикла l не надо модифицировать. После первого выполнения цикла rj
сбрасывается в 0.
Значение шага h используется в алгоритме два раза: при модификации
значения параметра цикла и при проверке условия выхода из цикла, поэтому
для хранения шага надо ввести дополнительную рабочую ячейку - rj+1.
Конечное значение параметра цикла c используется в алгоритме один раз
при формировании условия выхода из цикла.
При построении ПОЛИЗ надо передать на выход c именно в момент
формирования условия выхода из цикла.
Имя параметра цикла l также используется несколько раз, поэтому
вводим дополнительную ячейку для хранения имени параметра цикла и
признак цикла, значение которого используется для заполнения ячейки с
именем параметра цикла.
Итак, в соответствии с блок-схемой получим ПОЛИЗ:

l a := rj 1:= mi: rj+1 h := rj 0 = mi+1 УПЛ l l rj+1 + := mi+1: rj 0 := l c


step to
- rj+1 * 0 ≤ mi+2 УПЛ A mi БП mi+2:
do ;

Сравнивая полученный код с исходным текстом оператора, определяем


дополнительные функции служебных слов и их приоритеты при построении
ПОЛИЗ.
- for - играет роль открывающей скобки, имеет приоритет 0, генерирует 3
рабочие метки; в стек записывает for mimi+1mi+2; в ПОЛИЗ ничего не заносит;
признак Цикл устанавливает в 1.
- символ := - действует по своему приоритету, а также, если признак
Цикл=1, то извлекает из ПОЛИЗ последнюю запись (это имя параметра цикла)
и записывает в ячейку Параметр цикла; после этого кл сбрасывает признак
Цикл в 0.
- step - является закрывающей скобкой для предшествующего выражения;
имеет приоритет 1; выталкивает из стека все до for исключительно; генерирует
рабочие ячейки rj, rj+1; генерирует в ПОЛИЗ rj 1 := mi: rj+1 (метку берет из стека).
- to - как и step имеет приоритет 1; выталкивает из стека все до for
исключительно; на выход (в ПОЛИЗ) генерирует запись
:= rj 0 := mi+1 УПЛ l l rj+1 + := mi+1: rj 0 := l.
- do - является закрывающей скобкой для предшествующего выражения, имеет
приоритет 1; выталкивает из стека все до for; генерирует на выход запись
- rj+1 * 0 ≤ mi+2 УПЛ.
- ; - (символ конца оператора) очищает стек до for; на выход генерирует
mi БП mi+2: и удаляет запись for из стека.
Рассмотрим пример
for l:=1 step 2 to 7 do a := a+1;
Процесс перевода в ПОЛИЗ показан на рис 6.1.
Результирующий текст ПОЛИЗ:
l1:= r1 1:= m1 : r2 2 := r1 0 =m2 УПЛ l l r2 + := m2 :r1 0 :=
l 7 – r2 * 0 ≤ m3 УПЛ a a 1 + := m1 БП m3 :
Т.к. счетчик рабочих ячеек освобождается по do , то для трансляции
программы с любыь количеством циклов надо только две рабочих ячейки.
Рассмотрим вложенный оператор цикла.
for l1:=a1 step h1 to c1 do for l2:=a2 step h2 to c2 do A ;
Его реализация приведена на рис. 6.2.
П l 1 := 2 := 7 - a a 1 +
О r1 r1 r2 :=
Л 1 0 * m1
И := = 0 БП
З m1: m2 ≤ m3:
r2 УПЛ m3
l УПЛ
l
r2
+
:=
m2:
r1
0
:=
l
Парам l l l l l l l l l l l l l
Цикл 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
С +
Т
Е
:= := :=
К for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3

Вх.цеп for l := 1 step 2 to 7 do a := a + 1 ;


П l1 a1 := h1 := c1 - l2 a2 := h2 := c2 - A m4
О r1 r1 r2 r1 r1 r2 Б
Л 1 0 * 1 0 * П
И := = 0 := = 0 m6
З m1: m2 ≤ m4: m5 ≤ :
r2 УПЛ m3 r2 УПЛ m6 m1
l1 УПЛ l2 УПЛ БП

l1 l2 m3

r2 r5 :

+ +
:= :=
m2: m5
r1 :
0 r1
:= 0
l1 :=
l2
Пар l1 l1 l1 l1 l1 l2 l2 l2 l2 l2
Цкл 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0
С
Т +
Е := :=
К := for m4m5m6 for m4m5m6 for m4m5m6 for m4m5m6 for m4m5m6
for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3
for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3 for m1m2m3

Вх. for l1 := a1 step h1 to c1 do for l2 := a2 step h2 to c2 do A ;


цеп
Генери Генери сч.раб. Генери Генери сч.раб.
ровать ровать ячеек - ровать ровать ячеек -
метки раб.яч. 2 метки раб.яч 2
m1m2 m3 r1,r2 m4m5 m6 r1,r2

Рис.6.2 (_продолжение)
Рассмотрим вариант оператора цикла типа арифметической прогрессии,
когда конечное значение указывается раньше, чем значение шага
for l := a to c step h do A ;
Блок-схема выполнения такого оператора следующая
l:=a

rj := 1
mi
:
rj+2 :=с

rj+1 :=h

не да
т rj = 0

l:=l+rj+1
mi+
1111
:

rj :=0

нет да
(l- rj+2) A
*rj+1≤0

mi+2:

Ошибки в блок-схеме
Построим ПОЛИЗ и сравним его с исходным текстом, установив
дополнительную нагрузку на служебные слова, получим:
l a := rj 1:= mi: rj+2 с := rj+1 h
to step
:= rj 0 = mi+1 УПЛ l l rj+1 + := rj 0 := l rj+2 - rj+1 * 0 ≤ mi+2 УПЛ A mi БП mi+2:
do ;
При реализации оператора цикла типа арифметической прогрессии
можно исключить использование признака rj, т.е. блок схему для оператора
for l := a step h until c do A ; представить следующим образом:

l := a
mi:

rj+1 :=h

нет да
(l-c) rj+1≤0

mi+1:
l:=l+rj+1

В этом случае по “;” надо генерировать l l rj+1 + := mi БП mi+1: , т.е. имя l и


значение шага rj+1 надо передавать через стек так же, как и метки, чтобы
правильно выполнять вложенные операторы.
Лекция 7.

Построение ПОЛИЗ оператора цикла,

содержащего список елементов.

Рассмотрим оператор цикла вида :

<оператор цикла>::=For <перем.>:= <список цикла> do <оператор>

<список цикла>::= <элеменет списка>| <список цикла>,<элеменет списка>


Элементы списка цикла могут быть 3-х видов:

– типа арифметического выражения For l:= a1, a2, ..., an do A;

– типа пересчета for l := a1 while b1, a2 while b2 do A;

– типа арифметической прогрессии for l := a1 step h1 to c1, a2 step h2 to c2 do A

где a, h, c – арифметические выражения; b – логическое выражение.

Необходимо учесть что:

– семантика элемента списка зависит от его типа;

– любой элемент списка начинается арифметическим выражением и


поэтому в начале перевода очередного элемента его тип неизвестен;

– в списке цикла различные элементы могут встречаться в произвольной


последовательности;

– желательно, чтобы алгоритм перевода был безвозвратным.

В операторах такого вида после выполнения тела цикла не определена


однозначно точка возврата, поэтому для трансляции таких операторов введем
дополнительные операции :
– БПВ – безусловный переход с возвратом, с тремя операндами-метками ;

– В – возврат, обеспечивающую резервирование свободной ячейки (для


команды возврата)

Операция БПВ, в ПОЛИЗ имеет вид

mp mq mr БПВ

и предписывает:

1. безусловный переход на метку mq

2. формирует в ячейке mr (зарезервированой операцией В) команду


безусловного перехода на метку mp.

Рассмотрим реализацию различных элементов списка цикла.

Элемент списка цикла типа арифметического выражения

Оператор вида :

for l:=a1, a2, ..., an do A; (1)

имеет следующую блок-схему выполнения:


Пусть i – номер первой свободной рабочей метки в момент начала
перевода оператора цикла.

Разберем оператор for l:=a1, a2 do A;

mi+2: mi+1: mi:В


for l:=a1 , a2 do A ; mi+4:
mi+3:

После присвоения l:=a необходимо перейти на оператор А, а после него


вернуться в точку следующего присвоения значения переменной l. Обозначим
меткой mi точку, соответствующую окончанию А, в которой будет
генерироваться операция перехода для возврата за новым значением l после
выполнения оператора А. Чтобы зарезервировать место для возврата,
воспользуемся операцией возврат В.

Начало оператора А пометим mi+1. Начало оператора цикла пометим mi+2


Метками mi+3, mi+4, ..., mi+n+2 точки, в которые надо возвращаться после
выполнения оператора А со значением l равным a1, a2, ..., an соответственно.
Оператор (1) даст следующую польскую запись:

mi+2: l a1 := mi+3 mi+1 mi БПВ mi+3: l a2 := mi+4 mi+1 mi БПВ mi+4: l ... mi+n+1: l an
, , ,
:= mi+n+2 mi+1 mi БПВ mi+1: A mi: B mi+n+2:
do :

Операции БПВ и В выбраны так, чтобы получить наиболее простой


алгоритм получения ПОЛИЗ.

Пример:

m2: m3: m1: m: В m4:


for l := a + b, 5 do k := l + 1;
m2: l a b + := m3 m1 m БПВ m3: l 5 := m4 m1 m БПВ m1: k l 1 + := m: В m4:
Элемент списка цикла типа пересчета.

Оператор вида (2):

mi+2: mi+3: mi+1: mi: В

for l := a1 while b1, a2 while b2 do A;

a1, a2 – арифметические выражения;

b1, b2 – логические выражения.

Обозначим меткой mi точку после оператора А, в которой операцией В


резервируется место для организации возврата на повторение цикла.

Обозначим меткой mi+1 начало оператора А.

Обозначим меткой mi+2 начало оператора цикла..

Обозначим метками mi+3, mi+4,..., mi+n+2 точки, в которые надо перейти


после выполнения цикла со значением l = a1, ...,an.

Проверка условий b1, b2, ... потребует использования операций УПЛ.


Оператор вида (2) дает следующую польскую запись:

mi+2: l a1 := b1 mi+3 УПЛ mi+2 mi+1 mi БПВ mi+3: l a2

for while ,

:= b2 mi+3 УПЛ mi+3 mi+1 mi БПВ mi+1: A mi: B mi+4:

while do

Фигурными скобками выделены части выходной строки, порождаемые


определенными символами оператора цикл .

Пример:

for l:=3 while x<0, -1 while x>0 do x:=x+l;

m2: m3: m1: m: B m4:

Получим ПОЛИЗ:

m2: l 3 := x 0 < m3 УПЛ m2 m1 m БПВ m3: l 1 @ := x 0 > m4 УПЛ

m3 m1 m БПВ m1: x x l + := m: B m4:

Если пронумеровать символы кода ПОЛИЗ, получим приведенные ниже


значения используемых меток, т.о. фрагменты метка: могут быть удалены из
кода.

m 31

m1 26

m2 1

m3 13

m4 32
Элемент списка цикла типа арифметической прогрессии

Оператор вида

for l:=a1 step h1 to c1, a2 step h2 to l c2 do A;

где a1, h1, c1, a2, h2, c2 – арифметические выражения.

Пусть ri – очередная свободна рабочая переменная. Тогда проставив


метки mi, mi+1, на конец и начало А, а также mi+2, mi+3, mi+4 и mi+5, mi+6, mi+7 на
каждый элемент списка и mi+8 на выход, получим:
mi+2: l a1 := rj 1 := mi+3: rj+1 h1 := rj 0 = mi+4 УПЛ l l rj+1 + := mi+4: rj 0 := l c1

for step to

– rj+1 sign * 0 ? mi+5 УПЛ mi+3 mi+1 mi БПВ mi+5: l a1 := rj 1 := mi+6 : rj+1 h2

, step

:= rj 0 = mi+7 УПЛ l l rj+1 + := mi+7: rj 0 := l c2

to

– rj+1 * 0 ? mi+8 УПЛ mi+6 mi+1 mi БПВ mi+1: А mi: B mi+8:

do ;
Лекция 8.
Алгоритм построения ПОЛИЗ оператора цикла со списком элементов.

Особенности имеют специфические символы оператора цикла: for := while step


, to do и конец оператора ; или end
При трансляции будем использовать два признака:
- признак «цикл» с двумя значениями 0 и 1.
- признак «тип элемента» со значениями 0, 1, 2 и 3.
0 – элемент списка цикла типа арифметического выражения
1 – элемент типа пересчета.
2 и 3 – типа арифметической прогрессии.

Кроме того, используется одна стандартная ячейка «параметра цикла» для


хранения идентификатора параметра цикла. Рассмотрим особенности работы
отдельных символов.
1. Символ for играет роль открывающей скобки, имеет приоритет 0.
a. По символу for в выходную строку записывается:
mi+2 :
b. в вершину стека:
for mi+3 mi+1 mi ( т.е for mp mq mr )
где:
mi – рабочая метка операции возврата В.
mi+1 – рабочая метка тела цикла ( начала оператора А).
mi+3 – рабочая метка участка программы, которая для элемента типа 0 и 1
вычисляет второй элемент списка цикла, а для элемента 3 засылает в рабочую
ячейку значение шага;
c. формируются признаки: «цикл»=1 и «тип элемента»=0, т.е.
предполагается, что первый элемент списка цикла имеет тип «ар.выражение».
Примечание: первый пункт выполняется «про запас», на случай, если первым
элементом списка цикла окажется элемент пересчета. Для элементов другого типа
эта метка не используется.
2. Символ « := » имеет свои особенности, если встречается в операторе
цикла. Если признак «цикл»=1, это значит, что последняя запись выходной строки,
т.е. ПОЛИЗ, является именем параметра цикла (  ).
a. символ := в стек не заносится.
b. идентификатор параметра цикла  записывается в стандартную
ячейку «параметр цикла».
c. затем признак «цикл»=0.
3. Символ « , » является закрывающей скобкой для предшествующего
выражения и имеет приоритет 1, который обеспечивает:
a. выталкивание из стека всех знаков до записи
for mp mq mr
исключительно (запись занесена при обработке for)
b. Дальнейшие действия зависят от значения признака «тип
элемента».
Значение признака Запись в вых. строку Запись в стек
«тип элемента»
0 – «арифметичес- := mp mq mr БПВ mp:  for mp+1 mq mr
кое. выражение»
1 – «пересчет» mp УПЛ mp-1 mq mr БПВ mp:  for mp+1 mq mr
2 – ошибка: после - Занесение в таблицу
step нет to ошибок.
3– – rj+1 * 0 ≤ mp+2 УПЛ mp mq mr for mp+3 mq mr
«арифметическая БПВ mp+2:  «счетчик рабочих
прогрессия» ячеек» := «счетчик
рабочих ячеек» – 2

Примечание: в графе «Запись в стек» указана запись, которая


заносится в вершину стека вместо записи for mp mq mr.
c. После этого признаку «тип элемента» присв. 0.
4. Символ do является закрывающей скобкой для предшествующего выражения
и всего заголовка, следовательно, он имеет приоритет 1.
a. Все знаки до
do mp mq mr
исключительно выталкиваются из устека.
b. Дальнейшие действия зависят от типа элемента цикла.
Значение признака Запись в вых. строку Запись в стек
«тип элемента»
0– := mp mq mr БПВ mp: do mp+1 mq mr
«арифметическое
выражение»
1 – «пересчет» mp УПЛ mp-1 mq mr БПВ mp: do mp+1 mq mr
2 – ошибка: после - Занесение в таблицу
step нет to ошибок.
3– – rj+1 * 0 ≤ mp+2 УПЛ mp mq mr do mp+3 mq mr
«арифметическая БПВ mq: «счетчик рабочих
прогрессия» ячеек» := «счетчик
рабочих ячеек» – 2

Запись, определяемая таблицей, заносится в стек вместо for mp mq mr.


c. Признак «тип элемента» устанавливаем в 0.
5. Символ while является закрывающей скобкой для предшествующего
выражения в элементе списка цикла типа пересчета и имеет приоритет 1.
a. Выталкивает из стека все записи до
for mp mq mr
исключительно.
b. Затем в выходную строку заносит запись « := ».
c. Признак «тип элемента»=1.
Примечание: До этого признак «тип элемента» должен был иметь
значение 0. Другое значение – ошибка.
6. Символ step является закрывающей скобкой для предшествующего
выражения в элементе списка цикла типа арифметической прогрессии и
имеет приоритет 1.
a. Выталкивает из стека все записи до
for mp mq mr
b. В выходную строку заносит запись
:= rj 1 := mp : rj+1
c. Признаку «тип элемента» присваиваем 2.
Примечание: До этого признак «тип элемента» должен был иметь
значение 0. Иное значение – ошибка: неправильно записан
предыдущий элемент списка или неправильно записан заголовок,
например нет for.
7. Символ to, как и step, является закрывающей скобкой, имеет приоритет 1.
a. Выталкивает из стека все до записи
for mp mq mr
исключительно.
b. В выходную строку заносит запись
:= rj 0 = mp+1 УПЛ   rj+1 + := mp+1 : rj 0 := 
c. Признаку «тип элемента» присваиваем 3.
Примечание: До этого признак «тип элемента» должен был иметь
значение 2. Иначе – ошибка: перед to нет step.
8. Конец оператора (символы « ; » или end). Символы конца отрабатывают
согласно своим приоритетам:
a. Очищают стек вплоть до
for mp mq mr
Эта запись обозначает конец оператора цикла.
b. Тогда в выходную строку заносится запись
mr: B mp:
c. После этого запись for mp mq mr удаляется из стека и очистка
стека продолжается.
Примечание: Запись вида for mp mq mr может встречаться
несколько раз подряд (вложенные циклы), тогда каждый раз в
выходную строку заносится запись вида mr: B mp:

Пример: Перевести в ПОЛИЗ оператор


for x := y , 1 step 2 to 8 do y := y + x ;
m2: m3: m4: m5: m1: m:B m6:

m2: x y := m3 m1 m БПВ m3: x 1 := r1 1 := m4: r2 2 := r1 0 = m5 УПЛ x x r2 + := m5: r1 0 := x


for , step to
8 – r2 sign * 0 ≤ m6 УПЛ m4 m1 m БПВ m1: y y x + := m: B m6:
do ;

С учетом вышеприведенного алгоритма реализация перевода


представлена на рис 8.1.
ВЫХОД – полученный код ПОЛИЗ;
Пар. Цикла – имя параметра цикла, в нашем примере – х;
Ц – признак цикла, который используется для передачи имени параметра
цикла из кода ПОЛИЗ в ячейку Пар. Цикла;
Тип Эл. – признак, в исходном состоянии имеющий значение 0, которое в
дальнейшем меняется в зависимости от распознанного типа элемента списка
цикла;
СТЕК разделен на ячейки, первая запись for m3 m1 m модифицируется по
мере продвижения по оператору и используется для генерации соответствующих
фрагментов ПОЛИЗ
В m2 x y := 1 := 2 := 8 – y y x +
Ы : m3 r1 r1 r2 :=
Х m1 1 0 sign m
О m := = * :
Д БПВ m4 m5 0 B
Н m3 : УПЛ ≤ m6
А : r2 x m6 :
Я x x УПЛ
r2 m4
С + m1
Т := m
Р m5: БПВ
О r1 m1
К 0 :
А :=
x
Пар. - - x x x x x x x x x x x x x x x
цкла
Ц. 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Тип. 0 0 0 0 0 0 2 2 3 3 0 0 0 0 0 0 0
Эл.
С + +
Т := := := :=
Е for for for for for for for for for for for for for for for for
К m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3
m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1
m m m m m m m m m m m m m m m m
ВХ for x := y , 1 step 2 until 8 do y := y + x ;
СТР
Рис.8.1.
В m2 x y := 1 := 2 := 8 – y y x +
Ы : m3 r1 r1 r2 :=
Х m1 1 0 sign m
О m := = * :
Д БПВ m4 m5 0 B
Н m3 : УПЛ ≤ m6
А : r2 x m6 :
Я x x УПЛ
r2 m4
С + m1
Т := m
Р m5: БПВ
О r1 m1
К 0 :
А :=
x
Пар. - - x x x x x x x x x x x x x x x
цкла
Ц. 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Тип. 0 0 0 0 0 0 2 2 3 3 0 0 0 0 0 0 0
Эл.
С + +
Т := := := :=
Е for for for for for for for for for for for for for for for for for
К m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3 m3
m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 m1
m m m m m m m m m m m m m m m m m
ВХ for x := y , 1 step 2 until 8 do y := y + x ;
СТР
Рис.8.1.
Лекция 9. Другие формы внутренного представления программы

Построение тетрад при синтаксическом разборе "снизу вверх"

Кроме польской инверсной записи существуют и другие формы внутреннего


представления транслируемой программы. К ним относятся триады и
тетрады.

Тетрада представляется как:

<оператор>,<операнд1>,<операнд2>,<результат>

Таким образом a*b можно представить как

*,а,b,t

Выражение (a*b)+(c*d) представляется как

*,a,b,t1

*c,d,t2

+,t1,t2,t3

Терады расположены в порядке выполнения. Их недостатком является


большое число промежуточных переменных.

Триады имеют следующую форму:

<оператор> <операнд1>,<операнд2>

и не имеют поля для результата. Например, a+b*c запишеться как :

(1 * b , c

(2) + a , (1)
где (1) – это ссылка на результат 1-й триады. Выражение 1+b*c запишеться
как :

(1) * b , c

(2) + 1 , (1)

Рассмотрим более подробно тетрады. Принцип тетрад может быть


распространен на любые операторы. Например, можно показать такое
соответствие записи в ПОЛИЗ и в форме тетрад:

ПОЛИЗ Тетрады
ab+ + , a , b , t1
@a - , a , , t1
a b := := , b , ,a
m1 БП БП , m1
а m1 УПЛ УПЛ , m1 , a

Преобразование инфиксной записи в тетрады

Тетрады могут быть сгенерированы в результате работы любого алгоритма


синтаксического разбора.

Генерация тетрад на примере восходящего разбора.

Рассмотрим получение тетрад при восходящем разборе.

Проведем разбор арифметического выражения:

a*(-b+c)

Для него должны быть сгенерированы следующие тетрады:


- , b , , t1
+ , t1 , c , t2
* , a , t2 , t3

Грамматика для восходящего разбора приведена в левой колонке следующей


таблицы.

Правило грамматики Семантическая процедура


E1::=E E1.SEM:=E.SEM
E::=E+T1 j:=j+1
ENTER(“+”,E.SEM,T1.SEM.tj)
E.SEM:=tj
E::=E-T1 j:=j+1
ENTER(“-”,E.SEM,T1.SEM,tj)
E.SEM:=tj
E::=T1 E.SEM:=T1.SEM
E::= -T1 j:=j+1
ENTER(“-”,T1.SEM, ,tj)
E.SEM:=tj
T1::=T T1.SEM:=T.SEM
T::=T*F j:=j+1
ENTER(“+”,T.SEM,F.SEM,tj)
T.SEM:=tj
T::=T/F j:=j+1
ENTER(“/”,T.SEM,F.SEM,tj)
T.SEM:=tj
T::=F T.SEM:=F.SEM
F::=(E1) F.SEM:=E1.SEM
F::=i F.SEM:=i.SEM
В процессе разбора будем связывать имена идентификаторов (и
соответственно их значения) с нетерминалами, которыми они заменяются
при свертке.

Построим дерево разбора приведенного выражения в соответствии с


грамматикой. Дерево разбора приведено на следующем рисунке.

При движении по дереву нетерминальным символам ставим в


соответствие семантический эквивалент, т.е. имена идентификаторов
программы или дополнительных переменных для хранения промежуточных
результатов. При замене одного нетерминала другим семантический
эквивалент передается новому нетерминалу без изменения. Если в основу
входит операция, то свертка, т.е. замена основы новым нетерминалом,
сопоставляется с выполнением этой операции и новому нетерминалу, как
результату, сопоставляется новый семантический эквивалент.

Идея назначения семантического эквивалента может быть реализована в


виде семантических подпрограмм, сопровождающих правила грамматики
(см. правую колонку).

j- номер дополнительного имени для хранения результата операции

ENTER-процедура, которая генерирует тетраду и имя результата.

В процессе разбора получим:

Начальное значение j:=0.В дальнейшем будет сгенерировано:

j:=1 - , b , , t1 E.SEM:=t1

j:=2 + , t1 , c , t2 E.SEM:=t2

j:=3 * , a , t2 , t3 T.SEM:=t3

Реализация разбора следующая:


Стек Отн Вх Сем. п/п и тетрады
# < a*(-b+c) # i=0
#a > *(-b+c) #
# F,a > *(-b+c) # F.sem := a
# T,a = *(-b+c) # T.sem := F.sem := a
# T,a * < (-b+c) #
# T,a *( < -b+c) #
# T,a * ( - < b+c) #
# T,a * ( - b > +c) #
# T,a * ( - F,b > +c) # F.sem := b
# T,a * ( - T,b > +c) # T.sem := F.sem := b
# T,a * ( - T1,b > +c) # T1.sem := F.sem := b
# T,a * ( E, t1 < +c) # i=0+1=1
-,b, ,t1
E.sem := t1
# T,a * ( E, t1 + < c) #
# T,a * ( E, t1 + C > )#
# T,a * ( E, t1 + F,C > )# F.sem := C
# T,a * ( E, t1 + T,C > )# T.sem := F.sem := C
# T,a * ( E, t1 + T1,C > )# T1.sem := T.sem := C
# T,a * ( E, t2 > )# i=1+1=2
+, t1,C, t2
E.sem := t2
# T,a * ( E1, t2 = )# E1.sem := E.sem := t2
# T,a * ( E1, t2 ) > #
# T,a * F, t2 > # F.sem := E1.sem := t2
# T,t3 > # i=2+1=3
*,a, t2,t3
T.sem := t3
# T1,t3 > # T1.sem := T.sem := t3
# E,t3 > # E.sem := T1.sem := t3
# E1,t3 # E1.sem := E.sem := t3

Самостоятельно построить деревья разбора и получить последовательность


тетрад для следующих выражений:
a+b*c
a*b+c
Лекция 10. Генерация тетрад при рекурсивном спуске.

Тетрады, как и другие промежуточные представления программ, можно


получить в пезультате синтаксического разбора, в частности рекурсивного
спуска. Рассмотрим эту методику на примере арифметического выражения.
Грамматика для реализации рекурсивного спуска приведена ниже.

Z ::= E
E ::= [-] T { ( + | - ) T }
T ::= F { ( * | / ) F }
F ::= i | ( E )

Параллельно с выполнением процедур синтаксического разбора будем


выполнять процедуры передачи семантических эквивалентов каждому
нетерминалу и генерировать тетрады. Семантический эквивалент будем
представлять как параметр соответствующей процедуры.
Приведем схемы всех 4-х процедур в соответствии с правилами
грамматики.

i – номер текущей рабочей переменной ti (начально установлено i :=0)

proc Z (Z.sem)
call E (E.sem)
Z.sem := E.sem
end Z
proc E (E.sem)
if lex = “-” Вычисление
then семантического
begin эквивалента
call T (T.sem); i := i + 1; первого
генер. –, , T.sem, ti ; операнда
T.sem := ti ; двухместной
end арифметической
else call T (T.sem); операции +/-
while lex = “+” | “-” do
begin
EE := + | - , T.sem;
call T (T.sem); i := i + 1;
генер. EE, T.sem, ti ;
T.sem := ti ;
end
E.sem := T.sem
end E

proc T (T.sem)
call F (F.sem)
while lex = “*” | “/”do
begin
TT := * | / , F.sem
call F (F.sem); i := i + 1;
генер. TT, F.sem, ti ;
F.sem := ti ;
end;
T.sem := F.sem
end T
proc F (F.sem)
if lex = i then F.sem := i.sem;
if lex = ( then call E (E.sem); след. лекс.
if lex = ) then F.sem := E.sem;
end F

Символьную переменную EE ( TT ) вводим для хранения части


тетрады: знака и T.sem (F.sem), т.к. при определении второго операнда
операции значение T.sem (F.sem) будет утрачено, забито новым значением.
После определения семантического эквивалента второго операнда можно
генерировать всю тетраду, используя символьную переменную EE ( TT ).
Рассмотрим в качестве примера генерацию тетрад при разборке
выражения a * ( - b + c ) . Схема вызовов процедур и возврата семантического
эквивалента приведена на следующем рисунке.
Примеры:
Построить схему для следующих выражений a + b , a * b , a + b * c.
Лекция 11. Методы организации таблиц

Организация таблиц символов

Блок генерации кода требует наличия сведений об используемых в


программе идентификаторах. Эти сведения хранятся в таблице символов (или
таблице идентификаторов).

В процессе компиляции новый элемент добавляется в таблицу один


раз, но поиск ведётся всякий раз, когда встречается идентификатор. Так как на
этот процесс уходит много времени при компиляции, то важно выбрать такую
организацию таблиц, которая обеспечила бы эффективный поиск.

Эффективность работы с таблицами будем определять по времени


поиска, т.е. по числу сравнений, которые надо выполнить, чтобы найти данный
символ. Эффективность зависит не только то метода поиска, но и от
коэффициента загрузки таблицы, представляющего отношение текущего числа
элементов n к максимально возможному числу элементов таблицы N :

Неупорядоченные таблицы

Простейший способ организации таблиц состоит в том, чтобы добавлять


элементы в таблицу в порядке их поступления. Поиск в этом случае потребует
сравнения с каждым элементом таблицы, пока не будет найден нужный. Для
таблицы из n элементов в среднем будет выполнено n/2 сравнений. Если n
велико (20 или более), этот способ неэффективен.

Упорядоченные таблицы

Поиск может быть более эффективным, если элементы таблицы каким-


нибудь образом упорядочены, например, в лексикографическом (или
алфавитном) порядке. Так имена А, АВ, АВС, АС, ВВ расположены в
возрастающем порядке. Эффективным методом поиска в упорядоченном списке
является бинарный или логарифмический поиск.

Идея бинарного поиска заключается в следующем. Символ S, который


надо найти, сравнивается с элементом (n+I)/2 в середине таблицы. Если символ
не найден, мы должны просмотреть часть таблицы от I до (n+I)/2-I, если S
меньше среднего элемента, или часть от (n+I)/2+I до n, если S больше среднего
элемента таблицы. Затем повторяем процесс над половиной таблицы. Так как на
каждом шаге число элементов, которые могут содержать S сокращается вдвое,
то максимальное число сравнений, равно I+ log 2 n .

Если n=2, необходимо 2 сравнения, если n=4, то 3; если n=8, то 4. Для


n=128 требуется максимум 8 сравнений, в то время как для поиска в
неупорядоченной таблице потребуется в среднем 64 сравнения.

Процедура бинарного поиска может быть представлена

PROC BINPOISK

/* I – номер начала блока,

J - номер конца блока,

M – номер искомого элемента

N - размер таблицы Т */

I=1; J=N; M=0;

DO WHILE J  I  M=0

K=(I+J)/2;

IF S ≠ T(K)

THEN

DO

IF S<T(K)
THEN J=K-1;

ELSE I=K+1;

END;

ELSE M=K;

END;

END BINPOISK;

Так как таблица должна быть упорядоченной, то если заполнение


предшествует поиску, упорядочение можно выполнить после заполнения.
Если заполнение и поиск чередуются, то каждый новый элемент можно
добавлять в таблицу методом упорядоченных вставок. Пусть надо добавить в
таблицу элемент S. Поступают следующим образом:

1. Методом бинарного поиска ищется такое К , что Tk <S< Tk 1 .


2. Элемент n сдвигается в позицию n+1; n-1- в позицию n и т.д.,
элемент k+1- в позицию k+2.
3. S заносится в позицию к+1 (которая освободилась на шаге 2).

Хеш - адресация (перемешанные таблицы, функции расстановки,


рандомизация).

Это наиболее эффективный и широко применимый в компиляторах метод


работы с таблицами символов.

Метод заключается в преобразовании символа в индекс элемента в


таблице. Индекс получается «хешированием» символа- выполнением над
символом (и, возможно, над его длиной) некоторых простых арифметических
или логических операций.

Механизм хеширования состоит из функций хеширования (или


расстановки) h , таблицы хеширования и таблицы данных(см. рис. 2.1).
a) б)

Имя Указатель

.
.
Объект S
.

h(S) S

.
.
. информация об S

n-1

Рис.2.1. Схема памяти использующей


хеширование (расстановку)

а) таблица хеширования, б) таблица данных

Таблица данных может совпадать с таблицей хеширования, которая


имеет вид:

Имя Значение

Функция расстановки – это фактически набор функций h0 ,..., hm , каждая из


которых отображает набор объектов в множество целых чисел 0,1,…,N-1.
Функцию h0 будем называть первичной функцией расстановки. Когда
встречается новый объект S, функция h(S) указывает адрес объекта в таблице,
если он уже встречался, или адрес пустой ячейки таблицы, в которую надо
записать объект, если он встречается впервые.

Вычисление адреса таблицы расстановки.

Вход. Объект S, функция расстановки h, состоящая из


последовательности функций h0 , h1 ,..., hm , каждая из которых отображает
множество объектов в множество целых чисел 0,1,..., r  1, и таблица расстановки
с N ячейками.

Выход. Адрес расстановки h(S) и указание встречался ли объект S ранее.

Алгоритм

1. Вычисляем по порядку h0 ( S ), h1 ( S ), h2 ( s),..., hm ( S ) , выполняя шаг 2 до


тех пор, пока не исчезнет «конфликт». Если hm (S ) дает конфликт,
остановимся и сообщим о неудаче.
2. Вычисляем hi (S ) :

а) Если ячейка hi (S ) в таблице расстановки пуста, полагаем


h( S )  hi ( s) , сообщаем, что объект S ранее не встречался, и
останавливаемся.

б) Если ячейка hi (S ) не пуста, проверяем поле ее имени. Если


именем является S, полагаем h( S )  hi ( s) , сообщаем, что объект уже
встречался, и останавливаемся. Если именем является не S, то возник
конфликт, повторяем шаг 2 для вычисления другого адреса, т.е. hi 1 .

Вычисление каждого hi называется пробой таблицы.

Когда таблица расстановки слабо заполнена, конфликты


бывают редко и значение h для нового объекта можно вычислить
быстро (часто просто h0 ). По мере заполнения таблицы конфликты
становятся более частыми. Однако можно сконструировать хеш-таблицы
так, что по всем характеристикам они будут превосходить двоичный
поиск.

Пример. Возьмем n=10 и пусть объектами будут


произвольные цепочки латинских букв. Определим функции
h  CODE (S ) как сумму «порядковых номеров» каждой буквы в S,

считая, что А имеет порядковый номер I, B – 2 и т.д. Определим h j (S )

для 0  j  9 как h j  (CODE (S )  j ) mod10 , где а mod b – это остаток от


деления а на b.

В таблицу расстановки надо внести A,W и EF. Вычисляем


h0 ( A)  (1 mod10)  1 . Вносим А в позицию 1. Объект W вносим в

Имя Значение

0 данные

1 A для А

2 EF данные
для W
3 W
данные
4
для E F
5
.
6
.
7 .
8

Рис2.2 Содержимое хеш-таблицы.


позицию h0 (W )  (23 mod10)  3 . Вычисляем h0 ( EF )  (5  6) mod10  1 . Так как
позиция 1занята, пробуем h1 ( EF )  (5  6  1) mod10  2 . То для EF. отводится
позиция 2 (см. рис.2.2).

Пусть теперь надо выяснить, есть ли в таблице объект НХ.

Находим h0 ( HX )  2 . Проверяем позиция 2 и видим, что возник


конфликт. Проверяем позиция h1 ( HX )  3 . Снова конфликт. Наконец вычисляем
h2 ( HX )  4 и находим, что позиция 4 пуста. Приходим к выводу, что объекта НХ

в таблице нет.

Функции расстановки

Желательно пользоваться такой первичной функцией расстановки h0 ,


которая распределяла бы объекты равномерно по всей таблице расстановки.
Надо избегать функций, которые отображают не на все множество позиций или
выбирают одни позиции чаще, чем другие.

Наиболее широко используемые первичные хеш-функции:

Если S занимает в машине несколько слов, то на первом шаге


хеширования по S формируется одно машинное слово S’. Как правило S’
вычисляется суммированием всех слов при помощи обычного или поразрядного
сложения.

На втором шаге из S’ вычисляется окончательный индекс одним из


следующих способов.
1. Умножить S’ на себя и использовать n средних битов в
качестве значения функции хеширования (таблица 2 n
элементов). Так как n средних битов зависят от всех
битов S’, этот метод дает хорошие результаты.
2. Если в таблице 2 n элементов, то разбить S’ на n частей и
просуммировать их использовать n правых битов
результата.
3. S’ разделить на размер таблицы и взять в качестве
индекса остаток от деления.

В компиляторе PL/1 уровня F используется следующая хеш-


функция:

1. Суммируется последовательные части идентификатора,


содержащие по 4 литеры в один 4-байтный регистр.

2. Результат делиться на 211 и получается остаток R.

3. Берется значение 2R в качестве индекса для ссылки на


хеш-таблицу из 211 указателей (каждый указатель имеет длину 2
байта).

Хотелось бы, чтобы m=n-1 и чтобы hi  h j для любых i и j,


так как желательно найти в таблице пустую позицию, если она
есть.

Рассмотрим методы разрешения конфликтов; то есть


построения дополнительных функции h1 , h2 ,..., hm или
рехеширования. Метод разрешения конфликтов оказывает
влияние на эффективность всей системы распределения памяти.

Простейшим набором функций является


hi S   h0 S   i mod N 1 i  N 1

Метод прост, но если возник конфликт, то занятые позиции


имеют тенденцию скапливаться.

Более эффективный метод получения вторичных адресов


заключается в выборе

hi S   h0 S   ri mod N 1 i  N 1

где ri - псевдослучайное число. Для этой цели достаточен


генератор случайных чисел, дающий все числа в диапазоне от 1
до N-1 по одному разу. Каждый раз, когда используються
вторичные функции, генератор устанавливается в одно и то же
состояние, то есть генеририруется одна и та же
последовательность чисел.

В качестве вторичных функций можно взять также

hi S   ih0 S   1mod N  
hi S   h0 S   a * i 2  b * i mod N

где а и b- подходящие константы.


Лекция 12
Эффективность методов рехеширования. Цепочки.
Линейное рехеширование
hi  (h0 ( S )  i ) mod N , 1  i  N  1

Предположим, что символы S1 и S2 были хешированны и записаны в


элементы 2 и 4 соответственно (рис.2.3). Теперь предположим, что символ S3
тоже ссылается на элемент 2.

0 0 0
1 1 1
2 S1 2 S1 2 S1
3 3 S3 3 S3
4 S2 4 S2 4 S2
5 5 5 S4
6 6 6
7 7 7

Рис.2.3

Возникает конфликт. Вычисляем h1 ( S )  3 и заносим S3. Наконец


предположим, что следующий символ S4 также ссылается на элемент 2.
Возникает последовательно 3 конфликта с S1, S3 и S2, т.к.
h0 ( S 4)  2, h1 ( S 4)  3, h2 ( S 4)  4 , наконец h3 ( S 4)  5 и символ S4 заносится в 5-

ю ячейку.
Низкая эффективность метода ясна из примера. После нескольких
конфликтов, разрешенных таким образом, элементы скапливаются вместе,
образуя данные последовательности заполненных элементов. Если
достигнута позиция n-1, переходим на позицию 0. Метод прост, но если
возникнет конфликт, то занятые позиции имеют тенденцию скапливаться.
Если h0(S) вызывает конфликт, то вероятность того, что h1(S) вызовет
конфликт выше средней.
Оценка среднего числа сравнений
 1  lf 
 
E  2 
1  lf
Т.о. зависимость ожидаемого числа сравнений Е от загрузки таблицы lf
будет следующей

lf E
10% 1,06
50% 1,5
90% 5,5

Заметим, что число сравнений зависит не от размера таблицы, а только от


степени ее заполнения.
В формуле линейного рехеширования вместо i можно найти лучшие
значения поправок, но даже в случае i этот метод быстрее бинарного поиска.
Предположим есть таблица из 1024 элемента заполненная наполовину, т.е.
512 элементов. При бинарном поиске мы ожидаем ( I  log2 n)  10 сравнений, а
здесь только 1,5.

Случайное рехеширование.
hi ( S )  (h0 ( S )  ri ) mod N , 1  i  N  1

Этот метод снимает проблему скопления. Если размер таблицы


представляется степенью двойки ( N  2 p ) , то хорошие результаты дает
использование генератора случайных чисел ri , предложенного Моррисом.
1. При вызове программы принять целое R равным 1.
2. Вычислять каждое ri следующим образом:
a. Установить R  R * 5 ;
b. R  R mod 4 N , т.е. взять p  2 младших разряда R и поместить в R ;
c. r  int(R / 4) , т.е. величину R сдвинуть вправо на 2 разряда и
результат присвоить r ;
Важнейшее свойство этого метода, что все числа ri различны.
Пусть N  8  23 , 4 N  32 , тогда генерируется следующие добавки ri :
R 1 5 25 29 17 21 9 13
R  5R 5 25 125 145 85 105 45 65
R  R mod 4 N 5 25 29 17 21 9 13 1
r  int( R / 4) 1 6 7 4 5 2 3 0
№ 1 2 3 4 5 6 7 8

Добавки ri все различны и покрывают все поля адресов.


При таком методе хорошее приближение ожидаемого числа сравнений дает
формула:
1
E    log(1  lf )
 lf 
Зависимость ожидаемого числа сравнений от загрузки будет следующей:

lf E

10% 1,05
50% 1,39
90% 2,56

При больших заполнениях таблицы используется следующая формула:


1
E .
n
1
N

Квадратичное рехеширование.
 
hi  h0 (S )  ai 2  bi  c mod N

Главная проблема состоит в том, чтобы обеспечить покрытие значениями


ri  ai 2  bi  c достаточно большого числа элементов таблицы. Оказывается,

что если размер таблицы N  2 p , то число просматриваемых элементов


слишком мало. Пусть a  1, d  1, c  0 размер таблицы N  8 и пусть h0 ( S )  3 .
Вычислим возможные hi (S )
i hi

1 (3  2) mod 8  5
2 (3  6) mod 8  1
3 (3  12) mod 8  7
4 (3  20) mod 8  7
5 (3  30) mod 8  1
6 (3  42) mod 8  5
7 (3  56) mod 8  3

Выбрано только 3 новых адреса: 1,5,7. Если N -простое число, то число


просматриваемых элементов составляет половину таблицы. Этот метод не
так хорош, как случайное хеширование, но здесь время вычисления ri
меньше, чем при случайном хешировании.

Метод цепочек.
Метод использует хеш-таблицу, элементы которой, называемые указателями,
в начальный момент равны 0, собственно таблицу символов вначале пустую,
и указатель, который указывает на ближайшее свободное место в таблице
символов. Элементы таблицы символов имеют дополнительное поле, которое
может содержать нуль или адрес другого элемента таблицы символов.
Начальное состояние всей системы следующее:

Хеш-таблица Таблица символов


имя значение доп. поле
1 0 1
2 0 2
3 0 3
4 0 4
5 0 5 POINTFREE
… … …
1

Дополнительное поле каждого элемента используется для того, чтобы


связать в цепочку элементы, для которых хеширование приводит к тому же
самому указателю.
Пусть символ S1 должен быть записан в таблицу символов. Функции
хеширования вырабатывает адрес, например 4. Это адрес хеш-таблицы. Если
хеш-таблице по этому адресу 0, то для записи элемента выполняются
следующие действия:
1. Внести элемент ( S1 , значение, 0) в позицию таблицы, на которую
указывает POINTFREE;
2. Занести содержимое POINTFREE в хеш-таблицу в элемент 4.
3. Прибавить 1 к указателю POINTFREE;
Получим:

Хеш-таблица Таблица символов


имя значение доп. поле
1 0 S1 0
2 0
3 0
4 1
5 0 POINTFREE
… … 2

Если поступают символы, для которых хеш-функция дает различные адреса


хеш-таблицы, процедура записи аналогична описанной.
Пусть мы записали символы S 2, S 3, S 4 , которые формировали адреса хеш-
таблицы 1,3 и 6 соответственно. В результате таблица будет выглядеть так

Хеш-таблица Таблица символов


имя значение доп. поле
1 2 S1 0
2 0 S2 0
3 3 S3 0
4 1 S4 0
5 0 POINTFREE
6 4
5

Пусть на вход поступает символ S5 и хеш-функция формирует для него адрес


хеш-таблицы 6. Содержимое хеш-таблицы по этому адресу не равен 0, а
равен адресу таблицы символов, по которому хранится элемент (S4,
значение, 0). Для разрешения конфликта выполняем следующее
1. Внести в дополнительное поле элемента, содержащего S 4 , значение
POINTFREE;
2. Записать по адресу POINTFREE в таблицу символов S 5 , значение, 0.
3. Прибавить 1 к указателю POINTFREE;
Т.о. после записи S 5 получим следующую структуру

Хеш-таблица Таблица символов


имя значение доп. поле
1 2 S1 0
2 0 S2 0
3 3 S3 0
4 1 S4 5
5 0 S5 0 POINTFREE
6 4
6

После записи символов S 6, S 7, S8 , которые ссылаются например на адреса


хеш-таблицы 4,3 и 3 соответственно получим следующую структуру.

Хеш-таблица Таблица символов


имя значение доп. поле
1 2 S1 6
2 0 S2 0
3 3 S3 7
4 1 S4 5
5 0 S5 0
6 4 S6 0 POINTFREE
S7 8
S8 0 9

Если таблица символов заполнилась, к ней можно всегда добавить новый


блок элементов (если допускается динамическое распределение памяти), т.к.
функция хеширования дает адрес хеш-таблице, а ссылки на таблицу
символов выполняется только с помощью указателей.
T.о., максимальное число элементов в таблице символов, в отличие от
методов рехеширования, не ограничено (но ограничено максимальное число
адресов хеш-таблицы). Заметим, что только хеш-таблица требует начальной
установки.
Размер хеш-таблицы обычно 100-300 элементов, а число элементов в таблице
символов много больше.
Как только все символы внесены в таблицу (например, после лексического
анализа), хеш-таблицу можно уничтожить (предполагается, что все
идентификаторы заменены командами лексем и номерами элементов в
таблице символов). Для метода цепочек требуется одно дополнительное
слово для каждого элемента, но в большинстве случаев это окупается.
Если предположить случайное распределение элементов, то средняя длина
поиска выражается
n 1
E  1
2N
Характерно, что даже при заполнении таблицы на 100%, т.е. n  N , средняя
длина поиска не превышает 1,5.
Рассмотренный нами вариант реализации называется методом внутренних
цепочек. Существует вариант внешних цепочек, когда таблица имеет вид

Хеш-таблица Таблица символов

1 S1 S6 0
2 0 S2 0
3 S3 S7 S8 0
4 S4
5 0 S5 0
6
Лекция 13.Статическое распределение памяти.
Память для данных.
Типы данных исходной программы должны быть отображены на типы
данных машины. Для некоторых типов это соответствие будет один к одному
(целое, вещественное,…), для других может понадобиться несколько
машинных слов для описания типа.
Память для массивов.
Элементы вектора размещаются последовательно в порядке возрастания
(или убывания) адресов. Существует несколько способов размещения
двухмерных массивов.
Обычный способ — хранение по строкам в порядке возрастания. Массив
A(1: M ,1: N ) будет расположен в порядке

A(1,1), A(1,2), A(1, N ), A(2,1), A(2, N ), A(M , N ) .

Тогда элемент A(i, j) находится в ячейке с адресом


ADDRESS ( A(1,1))  (i 1) * N  ( j 1) ,

который вычисляется в виде


( ADDRESS ( A(1,1))  N 1)  (i * N  j) .

Первое слагаемое является константой и вычисляется только 1раз. Таким


образом для вычисления адреса A(i, j) выполняется 1 умножение и 2
сложения.
Второй способ состоит в том, что выделяются отдельные области для каждой
строки и имеется вектор указателей для этих областей данных. Например,
описание массива A(1: M ,1: N ) порождает следующую структуру:

Указатель на строку 1 A(1,1)


Указатель на строку 2 A(2,1) A(1,2)

A(2,2) 
Указатель на строку М A( M ,1)
 A(1, N )
A( M ,2)
A(2, N )

A( M , N )
Вектор указателей хранится в той области данных, с которой ассоциируется
массив, а собственно массив хранится в отдельной области данных.
Адрес элемента массива A(i, j) есть (àäðåññåêòîðà óêàçàòåëåé i 1)  ( j 1) .

Преимущества 2-го метода:


1. Не надо использовать операцию умножения;
2. Не все строки могут находится оперативной памяти
одновременно;
В случае многомерного массива обращение ведется аналогично I случаю
двумерного(т.е. быстрее изменяются самые правые индексы).
Для многомерного массива A( L1 : U 1 , L2 : U 2 , Ln : U n ) определим, как
вычисляется адрес элемента A(i, j, k ,, l, m) .
Обозначим
d1  U 1  L1  1
d1  U 2  L2  1

d1  U n  Ln  1

т.е. d — число различных значений индекса  -го измерения.


Тогда адрес искомого элемента определяется как
BASE  (i  L1 ) * d 2 * d 3 ** d n  ( j  L2 ) * d 3 ** d n 
 (k  L3 ) * d 4 ** d n  (l  Ln1 ) * d n  m  Ln

Где BASE — адрес первого элемента A( L1 , L2 , Ln ) .


Выполнив умножение, разделим полученное выражение на постоянную и
переменную часть CONSTPART  VARPART
CONSPART  BASE  ((( L1 * d 2  L2 ) * d 3  L3 ) * d 2    Ln1 ) * d n  Ln )

VARPART  (((i * d 2 )  j ) * d 3    l ) * d n  m

CONSPART вычисляется один раз, оно зависит от расположения массива и его


размеров, а VARPART зависит от индексов элемента.
Вычисление VARPART можно упростить:
VARPART : ïåðâûé èíäåêñ (i )
VARPART : VARPART * d 2  âòîðîé èíäåêñ ( j )
VARPART : VARPART * d 3  òðåòèé èíäåêñ (k )

VARPART : VARPART * d n  n  é èíäåêñ (m)

Для выделения памяти под массив используется некий информационный


вектор. Структура вектора известна и память под него может быть отведена в
процессе трансляции. Память же под собственно массив может быть
отведена или в процессе трансляции, если верхняя и нижняя границы
массивов известны в о время трансляции, или во время выполнения
программы после входа в блок, где определяется **** массива.
Информационный вектор для массива A( L1 : U1 , L2 : U 2 , Ln : U n ) имеет
следующий вид:
L1 U1 d1

L1 U1 d1

… … …
L1 U1 d1

n CONSPART

BASE

Блоки и трансляция описаний.


Различаются 2 способа распределения памяти для размещения данных:
статический и динамический.
Статическое распределение состоит в назначении номеров для размещения
данных в процессе трансляции.
Динамическое распределение состоит в назначении адресов для размещения
данных в процессе исполнения программы. С этой целью транслятор
включает в объектную программу специальные команды, обеспечивающие
перераспределение памяти в ходе исполнения программы.
Блоки в языках определяют область действия описанных в них объектов и
предназначены, прежде всего, для экономии памяти в процессе исполнения
программы. Экономия достигается за счет размещения в одних и тех ячейках
памяти величин, описанных в независимых блоках.
Пример: При выполнении программы:

begin real a, b;
b := 0;
m1: begin real c, d;
b := b + 1;
m2: …
end m1;
n1: begin real f, q;
n2: if b = 1 then goto m1;…
end n1;
end;

К моменту достижения соответствующей метки распределение памяти


изменяется следующим образом:
m1 M2 n1 n2 m1 m2 n1 n2
Метка b=0 b=1 b=1 b=1 b=1 b=2 b=2 b=2
Ячейка
k+0 a a a a a a a a
k+1 b b b b b b b b
k+2 c f c f
k+3 d q d q

Переменные c,d и f,q последовательно сменяют друг друга в одних и тех же


ячейках памяти.
Эту блочную структуру можно представить в виде дерева:

Уровень1 Real a,b;

Уровень2 m1: real c,d; n1: real f,q;


Статическое распределение возможно, если количество данных, описанных в
блоке, не меняется при повторных входах в блок.
Статическое распределение невозможно, если:
 Применяются массивы с переменными границами и размеры
массивов определяются только при входе в блок;
 Используются рекурсивные процедуры, т.к. при их выполнении
требуется копирование области памяти выделенной процедуре,
причем до исполнения программы нельзя определить количество
копий.

Статическое распределение.
Проводится в два этапа.
На I этапе — выделяется секция памяти каждому блоку, на II — назначаются
адреса переменным внутри блока.
Пусть программа имеет следующую блочную структуру:

Б1 begin n1
begin n2
Б2
Б3 begin n3
Ур3
Ур1 end
begin n4
Б4
Ур3
end
Ур1 end;
begin n5
Б5
Б6 begin n6
Ур3
end
Ур2 begin n7
Б7
begin n8
Б8
Ур3
Ур4 end
end
end
end
Изобразим эту структуру следующим деревом:

n1 Уровень 1
n2 n5 Уровень 2
n3 n4 n6 n7 Уровень 3
n8 Уровень 4

Тогда память может быть распределена в соответствии со следующей


диаграммой:

N1

n1

n2 n5

n3
n7
Nmax
n6
n8
n4

Для статического распределения памяти составляется таблица блоков каждая


запись таблицы для блока i содержит ÓÁi - уровень блока, ni - к-во ячеек
памяти для размещения данных блока. В N 1 заносится адрес ячейки памяти
предшествующей 1-й ячейке поля данных.
Таблица Таблица указателей Поле данных
блоков начала блоков
N1
N1 N1
Блок 1 УБ1 = 1 УБ1 = 1 N2 n1 N5
n1 N2=N1+n1
Блок 2 УБ2 = 2 УБ2 = 2 N3 n2 N4 n5
N6 N7
n2 N3=N2+n2 Nmax
Блок 3 УБ3 = 3 УБ3 = 3 n3
n4 N8 n7
n3 N4=N3 n6
Блок 4 УБ4 = 3 УБ4 = 3 n8
n4 N5=N2
Блок 5 УБ5 = 2 УБ5 = 2
n5 N6=N5+n5
Блок 6 УБ6 = 3 УБ6 = 3
n6 N7=N6
Блок 7 УБ7 = 3 УБ7 = 3
n7 N8=N7+n7
Блок 8 УБ8 = 4 УБ8 = 4
n8 Nmax=N4+n4

Затем таблица блоков преобразуется в таблицу указателей начала блоков.


Вместо ni формируется указатель начала секции памяти для i  1 -го блока.
Для первого блока это N 1 , для второго N1  n1 , N max  max( N i  ni ) максимальное
значение — последняя ячейка занятая данными.
Если в блоке описать также и массивы, то они могут быть размещены в
области, отведенной для блока, но часты массивы располагают отдельно в
поле массивов (их иногда выносят на внешнюю память). Будем считать, что
массивы вынесены.
Второй этап — назначение адресов скалярным переменным внутри блока.
Для этого используют таблицу идентификаторов.
В общем случае таблица идентификаторов содержит:
 Идентификатор или ссылку на таблицу имен;
 Порядковый номер блока — i ;
 Уровень блока УБ;
 Порядковый номер идентификатора данного класса в блоку — j ;
 Признак, определяющий класс и тип идентификатора.(Классы:
простая переменная, массив, метка, метка, процедура. Для простых
переменных типы: вещественный, целый, логический и т.д.)
После составления таблицы указателей начала блоков относительно
адреса(порядковые номера) простых переменных j пересчитывается в
абсолютные адреса по формуле AÄÐÅÑ  N i  j .Абсолютные адреса заносятся
в таблицу идентификаторов вместо относительных. При статическом
распределении памяти под массивы поступают аналогично: на первом этапе
выделяется секция памяти каждому блоку, на втором **** начальные адреса
массивам внутри блока.
Различие состоит в том, что для каждого массива надо определить не только
его начальный адрес, но и место хранения определяющего вектора,
обеспечивающего вычисление адресов элементов массива (Оба эти
параметра заносятся в таблицу идентификаторов для каждого массива).
После распределение памяти для скалярных переменных и определяющих
векторов статическое распределение памяти под массив можно произвести
следующим образом:
 Определяется количество элементов каждого массива
(максимальное количество);
 Формируется таблица блоков (как и для скалярных переменных);
 Определяется указатель начала поля массивов (если поле массивов
следует за полем ****, то это N max );
 Таблица блоков преобразуется в таблицу указателей начала блоков
(такую же, как для скалярных переменных);
 Для всех блоков с номерами i  1,, m определяется адрес начала
каждого массива по формулам
b1  N i  1
b j  b j 1  n j 1 , j  2,..., k j

b j - адрес первой ячейки массива с номером j ,


j  2,..., k j ( k j — количество массивов в блоке i ),

N i - указатель начала секции памяти, выделенных блоку i , n j —

количество элементов массива с полем j (максимальное количество).


Лекция 14
Динамическое распределение памяти.

Недостатком статического распределения является неэкономное


распределение памяти (необходимо задавать максимальный размер
массивов), а преимуществом – экономия времени при исполнении
программы. Однако, если для массивов используется внешняя память, то
преимущество сводится на нет дополнительными затратами.
Динамическое распределение позволяет иметь единственную
сплошную область памяти для всех переменных, массивов, процедур и
обеспечивает простой способ обработки рекурсивных процедур. Кроме того,
наличие индексных регистров существенно облегчает динамическое
распркдкление памяти при исполнении программы. Поэтому часто
используется именно динамическое распределение.
Радикальным средством реализации динамического распределения
является стек. Рассмотрим такое распределение на примере. Пусть
программа имеет следующую структуру:

Пример.

p: begin real a,b;


real procedure p1(x,y);
real x,y;
begin real u,v;
end;
endp1;
a:=p1(a,b);
m1: begin real c,d;
m2: begin real f,..;
d:=p1(c,f);
m3: endm2;
n2:
endm1;
end.
Дерево, изображающее структуру программы, представим как:

p: Уровень 1
a, d

P1:
x, y, m1:
c, d Уровень 2
u,v

Уровень 3
m2:
f

Обращение к процедуре происходит из внешнего блока и из блока


m2. Указанное стрелками перемещение по дереву связано с динамаческим
перераспределением памяти.
Реализация:
Все данные, используемые в программе, размещаются в стеке. До
входа во внешний блок стек свободен, указатель стека (УС) содержит адрес
первой свободной ячейки.

УС СТЕК УНБ

Рис. 1. Исходное состояние стека.


В исходном состоянии в УС записана ссылка на начало стека.,
соответствующее метке р: , т.е. началу программы.
При входе в блок p в вершине стека выделяется ячейка для
размещения признака начала стека (0), а затем выделяются ячейки для всех
описанных и рабочих переменных . Количество ячеек np .Исполь зуется
также ячейка для хранения переменной i - значения уровня блока (УБ) и
массив указателей начала блоков УНБ.

УС СТЕК УНБ

[p] + np + 1 0 [p]
a
b
i

1

Рис.2. Состояние после входа в блок р .


Действия по подготовке стека соответствуют подпрограмме
НАЧАЛЬНЫЙ ВХОД.
НАЧ. ВХОД:

стек(1):=0;
i:=1;
УНБ(1):=УС
УС:=УС+np +1

Адреса переменных, входящих во внешний блок, вычисляются как


Динамическое распределение памяти.
При таком распределнии память представляет собой единый стек для
всех переменных, массивов, процедур

Пример.

p: begin real a,b;


real procedure p1(x,y);
real x,y;
begin real u,v;
end;
endp1;
a:=p1(a,b);
m1: begin real c,d;
m2: begin real f,..;
d:=p1(c,f);
m3: endm2;
n2:
endm1;
end.

УС Стек
p:
[p] --------->
Рис.1

до входа во внешний блок, стек свободен.

В исходном состоянии в УС записана ссылка на начало стека 0.


Затем выделяются ячейки для всех описанных рабочих переменных.
Действие по подготовке стека соответствующей процедуре
нач.вх;
стек(1):=0;
i:=1;
УНБ(1):=УС
УС:=УС+np +1

После входа в блок р состояние стека будет следующим


УС стек УНБ
[p]+np=1 >
p: 0 <-
| a <-- [p]
| b
| ........... Рис.2
>

Во внешнем блоке выполняется присваивание a:=p1(a,b); т.е.


обращение к процедуре, что соответствует переходу по дереву к
блоку p1: и распределении памяти.

Перераспределение памяти осуществляется:


При входе в блок соотв. процедуры/ф-и в ячейке стек (УС+1)
формируется запись сост-я из 3-х компонент: УНБ (УБк-1), УНБ(i), i

Указатель начала блока, в котором находится


1. описанная процедура, или блок, в который мы
переходим
2. УНБ, из которого произошло обращение к процедуре
3. Уровень блока из которого произошло обращение

Это есть связующая информация, обеспечивающая возврат после


выполнения процедуры.Затем i получает значение уровня блока, в
который произошел вход.Затем формируется новый елемент массива
УНБ - УНБ(i), в который заносится ссылка на связующую информацию,
т.е. на ячейку с меткой р1. Указатель стека увеличивается с учетом
переменных и процедур.

Пер: УС:=УС+1
вход:
стек(УС):=(УНБ(УБк-1),УНБ(k),i);
i:=УБк
УНБ(i):=УС
УС:=УНБ(i)+nk+1

УС стек УНБ
[p1]+np1+1 > p1: > 0 <- -- ---- [p]
| ^ a ^ <- [p1]
| | b | |
| | ............. | |
| | p1 | |
| ^- [p],[p1],1 ^< - -
| x
| y
| u
| v Рис.3
| .............
| -- >

Рис.3 -- Состояние после обращения к процедуре р1 из внешнего


блока

После выхода переменные локализированные в блоке р1 становятся


недоступными и восстанавливается доступ к локализированым
переменным в локализ. блоке из которого произошло обращение, т.е.
необходимо восстановить состояние стека которое было до
обращения к процедуре. Для этого используется связующая инф-я

(all,a2l,l )

a1l - УНБ, в котором описана процедура


-УНБ, из которого произишло
a2l
обращение к процедуре
- уровень блока, из которого
l
произошло обращение к процедуре

Прежде всего УНБ(l):= a2l это делает доступным переменные блока


уровня l,при этом, если l>2, то надо сделать доступными все
переменны явл. глобальными по отношентю к блоку уровня l.
Т.е. строим цепочку a1l ---a1l-1 ----- .........----a1 n и записываем все
значения этой цепочки, кроме последнего, в массив УНБ,
соответственно.Эти действия выполняет подпрограмма выхода из
процедуры.

Выход из процедуры
УС:=УНБ(i)-1
Из стека УНБ извлекаем ссылку
(a1l,a2l,l )
if l=1 then goto M2; else
УНБ(l):=a2l;
i:=l;
j:=l;
m1: if j=2 then goto M2:
Из стека УНБ(j) извлечь (a1l,a2l,l )
j:=l
УНБ(l):=a1l;
goto M1;
M2:

Восстанавливается состояние соответствующее рис.2.


После входа в блок m1 стек изменяется в соответствии с процедурой
вход и получаем

УС Стек УНБ
[m1]+nm1+1 >
|
| 0 <- [p]
| p: a [m1]
Состояние | b
стека .......
| p1 Доступны
после
входа в | [p] [p] 1 p и m1
блок m1 | c
| m1: d
| ......
i | Рис.4
2 |
|
--- >

При входе в блок m2, слева работает процедура вход:


УС Стек УНБ
[m2]+nm2+1 > 0
| a [p]
| p: b [m1]
| ......... [m2]
| [p] [p]
| 1
| c
|m1: d
| [m]
i | [m1]
3 |m2: 2
| f
| ......... Рис.5
- > рп

В блок m2 имеется обращение d:=p1(c,f) стек изменяется соств. проц.


ПФ
ПФ
Вход:

УС Стек УНБ
[p1]+np1+1 p: ..........
.......... <-- [p]
m1: .......... <-- [p]
.......... |
m2: .......... |
РП |
p1: [p] <-
i [p1] 3
2 | x
| y Рис.6
| u
- > v
.........

Вам также может понравиться