Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

spz / spz

.pdf
Скачиваний:
33
Добавлен:
23.02.2016
Размер:
5.16 Mб
Скачать

Системне програмне забезпечення.

11

int f_test(int a)

{int b,c; b = 0;

if (f_test_add(&b,&c) != 0) { return a; }

с = a + b;

}

Тут уже компілятор навряд чи зможе з'ясувати порядок зміни значення перемінних і виконання умов у даному фрагменті з двох функції (обидві вони самі по собі незалежно цілком осмислені!). Єдине попередження, що, швидше за все, одержить у даному випадку розроблювач, — це те, шануй функция f_test не завжди коректно повертає результат (отсутствует оператор return перед кінцем функції). І те це попередження насправді не буде відповідати щирому положенню речей.

У цілому перевірка додаткових семантичних угод є дуже корисною функцією компіляторів, але відповідальність за зміст вихідної програми завжди як і раніше залишається на її розроблювачі.

Ідентифікація лексичних одиниць мов програмування

Ідентифікація змінних, типів, процедур, функцій і інших лексичних одиниць мов програмування — це встановлення однозначної відповідності між даними об'єктами і їхніми іменами в тексті вхідної програми. Ідентифікація лексичних одиниць мови найчастіше виконується на етапі семантичного аналізу.

Як правило, більшість мов програмування вимагають, щоб у вхідній програмі імена лексичних одиниць не збігалися як між собою, так і з ключовими словами синтаксичних конструкцій мови. Однак часто цього буває недостатньо, щоб установити однозначну відповідність між лексичними одиницями та їх іменами, оскільки існують додаткові значеннєві (семантичні) обмеження, що накладаються мовою на вживання цих імен.

Наприклад, локальні змінні в більшості мов програмування мають так названу «область видимості», що обмежує вживання імені змінної рамками того блоку вхідної програми, де ця змінна описана. ЦЕ значить, що з одного боку, така змінна не може бути використана поза межами своєї області видимості. З іншого боку, ім'я змінної може бути не унікальним, оскільки в двох різних областях видимості допускається існування двох різних змінних з однаковим іменем (причому в більшості мов програмування, що допускають блокові структури, області видимості змінних у них можуть перекриватися).

Інший приклад такого роду обмежень на унікальність імен –це той факт, що в мові програмування С дві різні функції або процедури різними аргументами можуть мати одне й те саме ім'я.

Безумовно, повний перелік таких обмежень залежить від семантики конкретної мови програмування. Усі вони чітко задані в описі мови і не можуть допускати неоднозначності в тлумаченні, але не можуть бути цілком визначені на етапі лексичного розбору, а тому жадають від компілятора додаткових дій на етапах синтаксичного розбору і семантичного аналізу. Загальна спрямованість

Системне програмне забезпечення.

12

цих дій така, щоб дати кожній лексичній одиниці мови унікальне ім'я в межах усієї вхідної програми і потім використовувати це ім'я при синтезі результуючої програми.

Можна дати зразковий перелік дій компіляторів для ідентифікації змінних, констант, функцій, процедур і інших лексичних одиниць мови:

-імена локальних змінних доповнюються іменами тих блоків (функцій, процедур), у яких ці змінні описані;

-імена внутрішніх змінних і функцій модулів вхідної програми доповнюються іменами самих модулів, причому це стосується тільки внутрішніх імен і не повинне відбуватися, якщо змінна або функція доступні поза модулем;

-імена процедур і функцій, що належать об'єктам (класам), в об’єктноорієнтованих мовах програмування доповнюються найменуванням типу об'єкта (класу), якому вони належать;

-імена процедур і функцій модифікуються в залежності від типів їхніх формальних аргументів.

Звичайно, це не повний перелік можливих дій компілятора, кожна реалізація компілятора може припускати свій набір дій. Те, які з них будуть використовуватися і як вони будуть реалізовані на практиці, залежить від мови вхідної програми і розроблювачів компілятора. Як правило, унікальні імена, що компілятор привласнює лексичним одиницям мови, використовуються тільки у внутрішнім представленні вхідної програми компілятором, і людина, що створила вхідну програму, не зіштовхується з ними. Але вони можуть знадобитися користувачу в деяких випадках — наприклад, при налагодженні програми, при породженні тексту результуючої програми мовою ассемблера чи при використанні бібліотеки, створеної версією компілятора для однієї мови програмування в іншій мові (чи навіть просто в іншій версії компілятора). Тоді користувач повинний знати, за якими правилами компілятор породжує унікальні імена для лексичних одиниць вхідної програми3.

У багатьох сучасних компіляторах (і оброблюваних ними вхідних мовах) передбачені спеціальні настроювання і ключові слова, що дозволяють відключити процес породження компілятором унікальних імен для лексичних одиниць мови. Ці слова враховані в спеціальних синтаксичних конструкціях мови (як правило, це конструкції, що містять слова export чи external). Якщо користувач використовує ці засоби, то компілятор не застосовує механізм

3 Очень часто бывает, что пользователь не может вызвать и использовать по имени функцию (или процедуру), которая содержится в некоторой библиотеке. Сообщения компилятора вида «Функция такая-то не найдена» вызывают немало удивления, особенно когда пользователь сам создал библиотеку и точно знает, что эта функция в ней есть. Чаще всего проблема как раз связана с наименованием функций — компилятор дает функции в библиотеке не совсем то имя, которое дал ей пользователь в исходном коде. Правила, по которым происходит модификация имен, достаточно просты, можно их выяснить для конкретной версии компилятора и использовать при обращении к библиотеке. Однако лучше этого не делать, так как нет гарантии, что при переходе к другой версии компилятора (даже от той же фирмы-разработчика) эти правила не изменятся и код станет неработоспособным. Правильным средством будет аккуратное использование отключения механизма именования лексических единиц, предоставляемое синтаксисом исходного языка. Это надо сделать как при создании библиотеки, так и при обращении к ней. Тогда в принципе код не будет зависеть от типа компилятора и даже от входного языка.

Системне програмне забезпечення.

13

породження унікальних імен для зазначених лексичних одиниць. У цьому випадку розроблювач програми сам відповідає за унікальність імені даної лексичної одиниці в межах усієї вхідної програми або навіть у межах усього проекту (якщо використовуються кілька різних вхідних модулів). Якщо вимога унікальності не буде виконуватися, можуть виникнути синтаксичні або семантичні помилки на стадії компіляції або ж інші помилки на більш пізніх етапах розробки програмного забезпечення. Оскільки найбільше широко використовуваними лексичними одиницями в різних мовах програмування є, як правило, імена процедур або функцій, те це питання, насамперед, стосується саме їх.

Розподіл пам'яті. Принципи розподілу пам'яті

Розподіл пам'яті — це процес, що ставить у відповідність лексичним одиницям вхідної програми адресу, розмір і атрибути області пам'яті, необхідної для цієї лексичної одиниці. Область пам'яті — це блок комірок пам'яті, виділюваний для даних, якимось чином об'єднаних логічно. Логіка таких об'єднань задається семантикою вхідної мови.

Розподіл пам'яті працює з лексичними одиницями мови — змінними, константами, функціями і т.п. — і з інформацією про ці одиниці, отримані на етапах лексичного і синтаксичного аналізу. Як правило, вхідними даними для процесу розподілу пам'яті в компіляторі служать таблиця ідентифікаторів, побудована лексичним аналізатором, і декларативна частина програми (так називана «область описів»), отримана в результаті синтаксичного аналізу.

Не у всіх мовах програмування декларативна частина програми присутня явно, деякі мови передбачають додаткові семантичні правила для опису констант і змінних; крім того, перед розподілом пам'яті треба виконати ідентифікацію лексичних одиниць мови. Тому розподіл пам'яті виконується вже після семантичного аналізу тексту вхідної програми.

Процес розподілу пам'яті в сучасних компіляторах, як правило, працює з відносними, а не абсолютними адресами комірок пам'яті. Розподіл пам'яті виконується перед генерацією коду результуючої програми, тому що його результати повинні бути використані в процесі генерації коду.

В усіх мовах програмування існує поняття так званих «базових типів даних» (основних чи «скалярних» типів). Розмір області пам'яті, необхідний для лексичної одиниці базового типу, вважається заздалегідь відомим. Він визначається семантикою мови й архітектурою обчислювальної системи, на якій повинна виконуватися створена компілятором результуюча програма. Розмір пам'яті, виділюваної під лексичні одиниці базових типів, не залежить від версії компілятора, що забезпечує сумісність і переносимість вхідних програм. Цього правила строго дотримують ведучі розроблювачі компіляторів.

Ідеальним варіантом для розроблювачів програм був би такий компілятор, у якого розмір пам'яті для базових типів залежав би тільки від семантики мови. Але найчастіше залежність результуючої програми від архітектури обчислювальної системи цілком виключити не вдається. Тоді творці компіляторів і мов програмування розробляють механізми, що дозволяють звести цю залежність до мінімуму.

i 1,n
де п — загальна кількість полів у структурі, Vполя. — обсяг пам'яті для i-го поля структури;
- для об'єднань: V стр = max Vполя i,
i 1,n
де- п — загальна кількість полів в об'єднанні, Vполя і. — обсяг пам'яті для i- го полю або об'єднання.
Для більш складних структур даних вхідної мови обсяг пам'яті, що відводиться під ці структури даних, обчислюється рекурсивно. Наприклад, якщо є масив структур, то при обчисленні обсягу пам'яті, що відводиться під цей масив, необхідний для одного елемента масиву, буде викликана процедура обчислення пам'яті структури. Такий підхід визначення обсягу займаної пам'яті
V

Системне програмне забезпечення.

14

Наприклад, у мові програмування С базовими типами даних є типи char, int, long int і т.і. (реально цих типів, звичайно, більше, ніж три), а в мові програмування Pascal — типи byte, char, word, integer і т.п. Розмір базового типу int у мові С для архітектури комп'ютера на базі 16-розрядних процесорів складає 2 байти, а для 32-розрядних процесорів — 4 байти. Розроблювачі вхідної програми на цій мові, звичайно, можуть довідатися дану інформацію (вона, як правило, відома для кожної версії компілятора), але якщо її використовувати в програмі прямо, то така програма буде жорстко прив'язана до конкретної архітектури комп'ютера. Щоб виключити подібну залежність, краще використовувати механізм визначення розміру пам'яті для типу даних, наданий мовою програмування, — у мові С, наприклад, це функція sizeof.

Для більш складних структур даних використовуються правила розподілу пам'яті, обумовлені семантикою (змістом) цих структур. Ці правила досить прості й у принципі однакові у всіх мовах програмування. От правила розподілу пам'яті під основні види структур даних:

-для масивів — добуток числа елементів у масиві на розмір пам'яті для одного елемента (те ж правило застосовне і для рядків, але в багатьох мовах рядка містять ще і додаткову службову інформацію фіксованого обсягу);

-для структур (записів з іменованими полями) — сума розмірів пам'яті по всіх полях структури;

-для об'єднань (союзів, загальних областей, записів з варіантами) — розмір максимального полючи в об'єднанні;

-для реалізації об'єктів (класів) — розмір пам'яті для структури з такими ж іменованими полями плюс пам'ять під службову інформацію об’єктно-орієнтованої мови (як правило, фіксованого обсягу).

Формули для обчислення обсягу пам'яті можна записати в такий спосіб:

-для масивів: Vмас =П (mi )*Vел ,

і 1,n

де п — розмірність масиву, m, — кількість елементів i-й розмірності, Vел

— обсяг пам'яті для одного елемента; - для структур: Vcmp = поля і ,

Системне програмне забезпечення.

15

дуже зручний, якщо декларативна частина мови представлена у виді дерева типів. Тоді для обчислення обсягу займаної типом з кожної вершини дерева, потрібно обчислити обсяг пам'яті для всіх нащадків цієї вершини, а потім застосувати формулу, зв'язану безпосередньо із самою вершиною. (цей механізм

подібний механізму Су-перекладу, застосовуваному при генерації коду). Саме такого типу

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

Далеко не всі лексичні одиниці мови вимагають для себе виділення пам'яті. Те, під які елементи мови потрібно виділяти комірки пам'яті, а під які ні, визначається винятково реалізацією компілятора й архітектурою використовуваної обчислювальної системи. Так, цілочисельні константи можна розмістити в статичній пам'яті, а можна і безпосередньо в тексті результуючої програми (це дозволяють практично всі сучасні обчислювальні системи), те ж саме відноситься і до констант із плаваючою крапкою, але їхнє розміщення в тексті програми припустимо не завжди. Крім того, з метою економії пам'яті, займаною результуючою програмою, під різні елементи мови компілятор може виділити ті самі комірки пам'яті. Наприклад, в одній і тій же області пам'яті можуть бути розміщені однакові строкові константи або дві різні локальні змінні, які ніколи не використовуються одночасно.

Говорячи про обсяг пам'яті, займаної різними лексичними одиницями і структурами даних мови, варто згадати ще один момент, зв'язаний з вирівнюванням границь областей, що відводяться для різних лексичних

одиниць, пам'яті. Архітектура багатьох сучасних обчислювальних систем передбачає, що обробка даних виконується більш ефективно, якщо адреса, по якій вибираються дані, кратна визначеному числу байт (як правило, це 2, 4, 8 чи 16 байт)1. Сучасні компілятори враховують особливості обчислювальних систем, на які орієнтована результуюча програма. При розподілі даних вони можуть розміщати області пам'яті під лексичні одиниці найбільш оптимальним чином. Оскільки не завжди розмір пам'яті, що відводиться під лексичну одиницю, кратний зазначеному числу байт, то в загальному обсязі пам'яті, що відводиться під результуючу програму, можуть з'являтися невикористовувані області. І Наприклад, якщо ми маємо опис змінних мовою С:

static char cl, с2, с3;

то, знаючи, що під одну змінну типу char приділяється 1 байт пам'яті, можемо очікувати, що для описаних вище змінних буде потрібно всього 3 байти пам'яті. Але якщо кратність адрес для доступу до пам'яті встановлена 4 байти, to під ці змінні буде відведено в цілому 12 байт пам'яті, з яких 9 не будуть використовуватися.

Як правило, розроблювачу вихідної програми не потрібно знати, яким чином компілятор розподіляє адреси під області пам'яті. Найчастіше компілятор сам вибирає оптимальний метод, і, змінюючи границі виділених областей, він завжди коректно здійснює доступ до них. Питання про це може встати, якщо з даними програми, написаній на одній мові, працюють програми, написані на іншій мові програмування (найчастіше, мовою асемблера), рідше такі проблеми виникають при використанні двох різних компіляторів з тої самої вхідної мови. Більшість компіляторів дозволяють користувачу в цьому випадку самому вказати, чи використовувати кратні адреси і яку границю кратності установити

Системне програмне забезпечення.

16

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

Пам'ять можна розділити на локальну і глобальну пам'ять, динамічну і статичну пам'ять.

Глобальна і локальна пам'ять

Глобальна область пам'яті — це область пам'яті, що виділяється один раз при ініціалізації результуючої програми і діє увесь час виконання програми. Як правило, глобальна область пам'яті може бути доступна з будь-якої частини вихідної програми, але багато мов програмування дозволяють накладати синтаксичні і семантичні обмеження на доступність навіть для глобальних областей пам'яті. При цьому самі області пам'яті і зв'язані з ними лексичні одиниці залишаються глобальними, обмеження накладаються тільки на можливість використання їх у тексті програми вхідною мовою (і ці обмеження в принципі можливо обійти).

Локальна область пам'яті — це область пам'яті, що виділяється на початку виконання деякого фрагмента результуючої програми (блоку, функції, процедури або оператора) і може бути звільнена по завершенні виконання даного фрагмента. Доступ до локальної області пам'яті завжди заборонений за межами того фрагмента програми, у якому вона виділяється. Це визначається як синтаксичними і семантичними правилами мови, так і кодом результуючої програми. Навіть якщо удасться обійти обмеження, що накладаються вхідною мовою, використання таких областей пам'яті поза їхньою областю видимості приведе до катастрофічних наслідків для результуючої програми.

Розподіл пам'яті на локальні і глобальні області цілком визначається семантикою мови вихідної програми. Тільки знаючи зміст синтаксичних конструкцій вихідної мови, можна чітко сказати, яка з них буде віднесена в глобальну область пам'яті, а яка — у локальну. Іноді у вихідній мові для деяких конструкцій немає чіткого розмежування, тоді рішення про їхнє віднесення в ту чи іншу область пам'яті приймається розроблювачами компілятора і може залежати від використовуваної версії компілятора. При цьому розробники вхідної програми не повинні покладатися на той факт, що один раз прийняте рішення буде незмінним у всіх версіях компілятора.

Розглянемо для приклада фрагмент тексту модуля програми мовою

Pascal:

const

Global_1= 1; Global_2 : integer = 2;

var

Global_ I : integer;

function Test (Param: integer): pointer; const

Local_l = 1;

Системне програмне забезпечення.

17

Local_2 : integer = 2;

var

Local_I : integer;

begin

end;

Відповідно до семантики мови Pascal, змінна Global_I є глобальною змінною мови і розміщається в глобальній області пам'яті, константа Global_1 також є глобальної, але мова не вимагає, щоб компілятор обов'язково розміщав її в пам'яті — значення константи може бути безпосередньо підставлене в код результуючої програми там, де вона використовується. Типізована константа Global_2 є глобальною, але на відміну від константи Global_l семантика мови припускає, що ця константа обов'язково буде розміщена в пам'яті, а не в коді програми. Приступність ідентифікаторів Global_I, Global_2 і Global_l з інших модулів залежить від того, де і як вони описані. Наприклад, для компілятора Borland Pascal змінні і константи, описані в заголовку модулів, доступні з інших модулів програми, а змінні і константи, описані в тілі модулів, недоступні, хоча і ті й інші є глобальними.

Параметр Param функції Test, змінна Local_I і константа Local_1 є локальними елементами цієї функції. Вони не доступні поза межами даної функції і розташовуються в локальній області пам'яті. Типізована константа Local_2 являє собою дуже цікавий елемент програми. З одного боку, вона не доступна поза межами функції Test і тому є її ЛОКАЛЬНИМ елементом. З іншого боку, як типізована константа вона буде розташовуватися в глобальній області пам'яті, хоча нізвідки ззовні не може бути доступна (аналогічними конструкціями мови С, наприклад, є локальні змінні функцій, описані як static). Семантичні особливості мови повинні враховувати творці компілятора, коли розробляють модуль розподілу пам'яті. Безумовно, це повинен знати і розроблювач вхідної програми, щоб не допускати семантичних (значеннєвих) помилок – цей тип помилок складніше всього піддається виявленню. Так, у приведеному вище прикладі як результат функції Test може виступати адреса змінної Global_I чи типізованої константи Global_2. Результатом функції не може бути адреса констант Global_I чи Local_I –це заборонене семантикою мови. Набагато складніше питання із змінною Local_I чи параметром функції Param. Будучи елементами локальної області пам'яті функції Test, вони ніяк не можуть виступати як результат функції, тому що він буде використаний поза самою функцією, коли область її локальної пам'яті може вже не існувати. Але ні синтаксис, ні семантика мови не забороняють використовувати адресу цих елементів як результат функції (і далеко не завжди компілятор здатний хоча б знайти факт такого використання адреси локальної перемінної). Ці особливості мови повинні враховувати розроблювач програми. А от адреса типізованої константи Local_2 у принципі може бути результатом функції Test. Хоча вона і є локальною константою, але розміщається в глобальній області пам'яті (інша справа, що цим користуватися не рекомендується, оскільки немає гарантії, що принцип розміщення локальних типізованих констант не зміниться з переходом до іншої версії компілятора).

Системне програмне забезпечення.

18

Статична і динамічна пам'ять

Статична область пам'яті — це область пам'яті, розмір якої відомий на етапі компіляції. Оскільки для статичної області пам'яті відомий її розмір, компілятор завжди може виділити цю область пам'яті і зв'язати її з відповідним елементом програми. Тому для статичної області пам'яті компілятор породжує деяку адресу, як правило, це відносна адреса в програмі. У статичну область пам'яті попадає більшість змінних і констант вхідної мови.

Статичні області пам'яті обробляються компілятором самим найпростішим чином, оскільки прямо зв'язані зі своєю адресою.

Динамічна область пам'яті — це область пам'яті, розмір якої на етапі компіляції програми не відомий. Розмір динамічної області пам'яті буде відомий тільки в процесі виконання результуючої програми. Тому для динамічної області пам'яті компілятор не може безпосередньо виділити адресу

— для неї він породжує фрагмент коду, що відповідає за розподіл пам'яті (її виділення і звільнення). Як правило, з динамічними областями пам'яті зв'язані багато операцій з покажчиками і з екземплярами об'єктів (класів) в об’єктноорієнтованих мовах програмування.

Динамічні області пам'яті, у свою чергу, можна розділити на динамічні області пам'яті, виділювані користувачем, і динамічні області пам'яті, виділювані безпосередньо компілятором.

Динамічні області пам'яті, виділювані користувачем, з'являються в тих випадках, коли розроблювач вхідної програми явно використовує в тексті програми функції, зв'язані з розподілом пам'яті (прикладом таких функцій є

New і Dispose у мові Pascal, malloc і free у мові і C, new і delete у мові С ++ і

ін.). Функції розподілу пам'яті можуть використовувати або прямо засоби ОС, або засоби вихідної мови (які, зрештою, усе рівно засновані на засобах ОС). У цьому випадку за своєчасне виділення і звільнення пам'яті відповідає сам розроблювач, а не компілятор. Компілятор повинний тільки побудувати код виклику відповідних функцій і збереження результату - у принципі, для нього робота з динамічною пам'яттю користувача нічим не відрізняється від роботи з будь-якими іншими функціями і додатковими складностями не викликає.

Інша справа — динамічні області пам'яті, виділювані компілятором. Вони з'являються тоді, коли користувач використовує типи даних, операції над якими припускають перерозподіл пам'яті, що не присутній у явному виді в тексті вхідної програми. Прикладами таких типів даних можуть служити рядки в деяких мовах програмування, динамічні масиви і, звичайно, багато операцій над екземплярами об'єктів (класів) в об’єктно-орієнтованих мовах (найбільш характерний приклад — тип даних string у версіях Borland Delphi мови Object Pascal). У цьому випадку сам компілятор відповідає за породження коду, що буде завжди забезпечувати своєчасне виділення пам'яті під елементи програми, і за звільнення її в міру використання. Багато компіляторів об’єктноорієнтованих мов програмування використовують для цих цілей спеціальний менеджер пам'яті, до якого звертаються у всіх випадках при явному чи неявному використанні динамічної пам'яті. Код менеджера пам'яті включається в текст результуючої програми або поставляється у виді окремої бібліотеки.

Системне програмне забезпечення.

19

Як статичні, так і динамічні області пам'яті самі по собі можуть бути глобальними чи локальними.

Системне програмне забезпечення.. І.Яковлєва

1

Способи внутрішнього представлення програм

1.Зв'язані облікові структури, що представляють синтаксичні дерева.

2.Багатоадресний код з явно іменованим результатом (тетради).

3.Багатоадресний код з неявно іменованим результатом (тріади).

4.Обернений (постфиксна) польський запис операцій.

5.Алгоритм Дейкстри.

6.Асемблерний код або машинні команди.

7.Розбір арифметичного виразу. Алгоритм Рутисхаузера.

Можливі різні форми внутрішнього представлення синтаксичних конструкцій вихідної програми в компіляторі. На етапі синтаксичного розбору часто використовується форма, іменована деревом виведення. Але форми представлення, використовувані на етапах синтаксичного аналізу, виявляються незручними в роботі при генерації й оптимізації об'єктного коду. Тому перед оптимізацією і безпосередньо перед генерацією об'єктного коду внутрішнє представлення програми може перетворюватися в одну з відповідних форм запису.

Усі внутрішні представлення програми звичайно містять у собі дві принципово різні речі — оператори й операнди. Розходження між формами внутрішнього представлення полягають лише в тому, як оператори й операнди з'єднуються між собою. Також оператори й операнди повинні відрізнятися один від іншого, якщо вони зустрічаються в будь-якому порядку. За розрізнення операндів і операторів, як уже було сказано вище, відповідає розроблювач компілятора, що керується семантикою вхідної мови.

Відомі наступні форми внутрішнього представлення програм1 :

1.зв'язані облікові структури, що представляють синтаксичні дерева;

2.багатоадресний код з явно іменованим результатом (тетради);

3.багатоадресний код з неявно іменованим результатом (тріади);

4.обернений (постфиксна) польський запис операцій;

5.асемблерний код або машинні команди.

У кожному конкретному компіляторі може використовуватися одну з цих форм, обрану розроблювачами. Але частіше компілятор не обмежується використанням тільки однієї форми для внутрішнього представлення програми. На різних фазах компіляції можуть використовуватися різні форми, що у міру виконання проходів компілятора перетворюються одна в іншу. Не усі з перерахованих форм широко використовуються в сучасних компіляторах, і про це буде сказано по мірі їхнього розгляду.

1 Існують три форми запису виразів — префіксна, інфіксна і постфіксна. При префиксному запису операція записується перед своїми операндами, при інфіксному між операндами, а при постфіксному — після операндів. Загальноприйнятий запис арифметичних виразів являє приклад інфіксного запису. Запис математичних функцій і функцій у мовах програмування є префіксним (інші приклади префіксного запису – команди ассемблера, тріади і тетради, в тому виді, як вони розглянуті далі). Постфіксний запис у повсякденному житті зустрічається рідко. З ним зіштовхуються тільки користувачі стекових калькуляторів і програмісти мовою

Forth.

Соседние файлы в папке spz