

1
Загальна схема роботи компіляторів
1.Визначення транслятора, компілятора, інтерпретатора.
2.Відмінність компілятора від транслятора.
3.Різниця між інтерпретаторами і трансляторами.
4.Етапи трансляції.
5.Загальна схема роботи транслятора.
6.Поняття проходу.
7.Багатопрохідні і однопрохідні компілятори.
Визначення транслятора, компілятора, інтерпретатора
Транслятор – це програма, що переводить вхідну програму на вихідній (вхідній) мові в еквівалентну їй вихідну програму на результуючій (вихідній) мові.
1.Транслятор є програмою — звичайно він входить до складу системного програмного забезпечення обчислювальної системи. Тобто транслятор — це частина програмного забезпечення (ПО), він являє собою набір машинних команд і даних і виконується комп'ютером, як і всі інші програми в рамках операційної системи (ОС). Усі складові частини транслятора являють собою фрагменти або модулі програми зі своїми вхідними і вихідними даними.
2.Вихідними даними для роботи транслятора служить текст вхідної програми — деяка послідовність речень вхідної мови програмування.
Звичайно це символьний файл, але цей файл повинний містити текст програми, що задовольняє синтаксичним і семантичним1 вимогам вхідної
мови. Крім того, цей файл несе в собі деякий зміст, обумовлений семантикою вхідної мови.
3.Вихідними даними транслятора є текст результуючої програми. Результуюча програма будується за синтаксичними правилами, заданим у вихідній мові транслятора, а її зміст визначається семантикою вихідної
мови. Важливою вимогою у визначенні транслятора є еквівалентність
1 Синтаксис мови — це набір правил, що визначає припустимі конструкції мови. Синтаксис визначає «форму мови» — задає набір ланцюжків символів, що належать мові. Найчастіше синтаксис мови можна задати у виді строгого набору правил, але цілком це твердження справедливе тільки для чисто формальних мов.
Семантика мови — це розділ мови, що визначає значення речень мови. Семантика визначає «зміст мови»
— задає значення для всіх припустимих ланцюжків мови. Семантика для більшості мов визначається неформальними методами (відносини між знаками і тим, що вони позначають, вивчаються семіотикою). Чисто формальні мови позбавлені якого-небудь змісту.
2
вхідної і вихідної програм. Еквівалентність двох програм означає збіг їхнього змісту з погляду семантики вхідної мови (для вихідної програми) і семантики вихідної мови (для результуючої програми). Без виконання цієї вимоги сам транслятор утрачає всякий практичний зміст.
Отже, щоб створити транслятор, необхідно насамперед вибрати вхідну і вихідну мови. З погляду перетворення речень вхідної мови в еквівалентні їм речення вихідної мови транслятор виступає як перекладач. Наприклад, трансляція програми з мови С на мову ассемблера по суті нічим не відрізняється від перекладу, скажемо, з російської мови на англійську, з тією тільки різницею, що складність мов трохи інша. Тому і саме слово «транслятор» означає «перекладач».
Результатом роботи транслятора буде результуюча програма, але тільки в тому випадку, якщо текст вихідної програми є правильним — не містить помилок з погляду синтаксису і семантики вхідної мови. Якщо вихідна програма неправильна (містить хоча б одну помилку), то результатом роботи транслятора буде повідомлення про помилку (як правило, з додатковими поясненнями і вказівкою місця помилки у вихідній програмі).
Визначення компілятора. Відмінність компілятора від транслятора
Крім поняття «транслятор» широко вживається також близьке йому за змістом поняття «компілятор».
Компілятор — це транслятор, що здійснює переклад вихідної програми в еквівалентну їй об'єктну програму мовою машинних команд або мовою ассемблера.
Таким чином, компілятор відрізняється від транслятора лише тим, що його результуюча програма завжди повинна бути написана мовою машинних кодів чи мовою ассемблера. Результуюча програма транслятора, у загальному випадку, може бути написана на будь-якій мові — можливий транслятор програм з мови Pascal на мову С. Відповідно, усякий компілятор є транслятором, але не навпаки — не всякий транслятор буде компілятором.
Наприклад, згаданий вище транслятор з мови Раscal на С компілятором бути не буде.
Саме слово «компілятор» походить від англійського терміна “compiler” («укладач», «компоновщик»). Термін зобов'язаний своєму походженню здатності компіляторів складати об'єктні програми на основі вихідних програм.
3
Результуюча програма компілятора називається «об'єктною програмою» чи «об'єктним кодом». Файл, у який вона записана, звичайно називається «об'єктним файлом». Навіть у тому випадку, коли результуюча програма породжується мовою машинних команд, між об'єктною програмою (об'єктним файлом) і програмою, що виконується, ( файлом, що виконується,) є істотна різниця. Породжена компілятором програма не може безпосередньо виконуватися на комп'ютері, тому що вона не прив'язана до конкретної області пам'яті, де повинні розташовуватися її код і дані .
Компілятори, безумовно, найпоширеніший вид трансляторів (багато хто вважають їхній узагалі єдиним видом трансляторів, хоча це не так). Вони мають саме широке практичне застосування, яким зобов'язані широкому поширенню всіляких мов програмування. Далі завжди будемо говорити про компілятори, припускаючи, що результуюча програма написана мовою машинних кодів або мові ассемблера.
Транслятори і компілятори, як і всі інші програми, розробляє людина або група розроблювачів. У принципі вони могли б створювати його безпосередньо мовою машинних команд, однак обсяг коду і даних сучасних компіляторів такий, що їх створення мовою машинних команд практично неможливо в розумний термін при розумних трудозатратах. Тому практично всі сучасні компілятори також створюються за допомогою компіляторів (звичайно в цій ролі виступають попередні версії компіляторів тієї ж фірми-виробника). І в цій якості компілятор є уже вихідною (выходящей) програмою для іншого компілятора, що нічим не краще і не гірше всіх інших породжуваних вихідних (выходящих) програм.
Визначення інтерпретатора. Різниця між інтерпретаторами і трансляторами
Крім схожих між собою понять «транслятор» і «компілятор» існує принципиально відмінне від них поняття інтерпретатора.
Інтерпретатор — це програма, що сприймає вхідну програму вихідною мовою і виконує її.
На відміну від трансляторів інтерпретатори не породжують результуючу програму (і взагалі якого-небудь результуючого коду) — і в цьому принципова різниця між ними.
Інтерпретатор, так само як і транслятор, аналізує текст вихідної програми. Однак він не породжує результуючої програми, а відразу ж виконує вихідну відповідно до її змісту, заданим семантикою вхідної мови. Таким чином, результатом роботи інтерпретатора буде результат, заданий змістом початкової програми, у тому випадку, якщо ця програма
4
правильна, або повідомлення про помилку, якщо початкова програма невірна.
Щоб виконати вихідну програму, інтерпретатор так чи інакше повинний перетворити її в мову машинних кодів, оскільки інакше виконання програм на комп'ютері неможливо. Він і робить це, однак отримані машинні коди є недоступними — їх не бачить користувач інтерпретатора. Ці машинні коди породжуються інтерпретатором, виповнюються і знищуються в міру потреби — так, як того вимагає конкретна реалізація інтерпретатора. Користувач же бачить результат виконання цих кодів — то результат виконання початкової програми.
Варто особливо згадати, що зараз у сучасних системах програмування стали з'являтися компілятори, у яких результуюча програма створюється не мовою машинних команд і не мовою ассемблера, а на деякій проміжній мові. Сама по собі ця проміжна мова не може безпосередньо виконуватися на комп'ютері, а вимагає спеціального проміжного інтерпретатора для виконання написаних на ньому програм. Хоча в даному випадку термін «транслятор» був би, напевно, більш правильним, у літературі вживається поняття «компілятор», оскільки проміжна мова є мовою дуже низького рівня, будучи родинною машинним командам і мовам ассемблера.
У першому поколінні компілятори писалися безпосередньо на машинних командах, але потом, з появою компіляторів, від цієї практики відійшли. Навіть самі відповідальні частини компіляторів створюються, як мінімум, із застосуванням мови ассемблера — а він теж обробляється компілятором.
Призначення трансляторів, компіляторів і інтерпретаторів. Приклади реалізації
Перші програми, що створювалися ще для ЕОМ першого покоління, писалися безпосередньо мовою машинних кодів. Це була пекельна робота.
З тих пір весь розвиток програмного забезпечення комп'ютерів нерозривно зв'язано з виникненням і розвитком компіляторів.
Першими компіляторами були компілятори з мов ассемблера або, як вони називалися, мнемокодів. Мнемокоди перетворили мову машинних команд у більш-менш доступну розумінню фахівця мову мнемонічних (переважно англомовних) позначень цих команд. Створювати програми стало значно, простіше, але виконувати сам мнемокод (мова ассемблера) жоден комп'ютер нездатний, відповідно, виникла необхідність у створенні компіляторів. Ці компілятори елементарно прості, але вони продовжують відігравати істотну роль у системах програмування донині.
Наступним етапом стало створення мов високого рівня. Мови високого рівня (до них відноситься більшість мов програмування) являють собою деяку проміжну ланку між чисто формальними мовами і мовами природного спілкування людей. Від перших їм дісталася формалізація синтаксичних структуру пропозицій мови, від других — значна частина

5
словникового запасу, семантика основних конструкцій і виразів (з елементами математичних операцій, що прийшли з алгебри).
Поява мов високого рівня істотно спростило процес програмування, хоча і не звело його до «рівня домогосподарки». Спочатку таких мов були одиниці, потім десятки, зараз, напевно, їх нараховується більш сотні. Процесу цьому не видно кінця. Проте як і раніше переважають комп'ютери традиційної, «неймановской»2, архітектури, що вміють розуміти тільки машинні команди, тому питання про створення компіляторів продовжує бути актуальним.
Як тільки виникла масова потреба в створенні компіляторів, стала розвиватися і спеціалізована теорія. Згодом вона знайшла практичне застосування у безлічі створених компіляторів. Компілятори створювалися і продовжують створюватися не тільки для нових, але і для давно відомих мов. Багато виробників від відомих, солідних фірм до мало кому знайомих колективів авторів випускають на ринок нові і більш нові зразки компіляторів.
Нині компілятори є невід'ємною частиною будь-якої обчислювальної системи. Без їхнього існування програмування будь-якої прикладної задачі було б утруднене, а те і просто неможливо. Та й програмування спеціалізованих системних задач, як правило, ведеться якщо не мовою високого рівня (у цій ролі в даний час найчастіше застосовується мова С), то мовою ассемблера, отже, застосовується відповідний компілятор.
Програмування безпосередньо на мовах машинних кодів відбувається винятково рідко і тільки для рішення дуже вузьких питань.
Компілятори, звичайно трохи простіші в реалізації, чим інтерпретатори. По ефективності вони також перевершують їх — очевидно, що відкомпільований код буде виконуватись завжди швидше, ніж відбувається інтерпретація аналогічної вихідної (исходной) програми. Крім того, не кожна мова програмування допускає побудову простого інтерпретатора. Однак інтерпретатори мають одну істотну перевагу — відкомпільований код завжди прив'язаний до архітектури обчислювальної системи, на яку він орієнтований, а вихідна (исходная) програма — тільки до семантики мови програмування, що набагато легше піддається стандартизації. Цей аспект спочатку не брали до уваги.
Першими компіляторами були компілятори з мнемокодів. Їхні нащадки — сучасні компілятори з мов ассемблера — існують практично для усіх відомих обчислювальних систем. Вони гранично жорстко
2 Неймановская архітектура. Джон фон Нейман, математик угорського походження, запропонував архітектуру з об'єднаною пам'яттю програм і даних. З тих пір це просте рішення широко застосовується в більшості комп'ютерів. Машина фон Неймана була створена в Принстонскому інституті новітніх досліджень у 1951 році. Вона містить три основних функціональних блоки – пам'ять, АЛУ і блок введення/виведення. Для виконання кожної операції АЛУ звертається по однієї і тій же лінії зв'язку спочатку до пам'яті програм, а потім до пам'яті даних. Пристрій уведення/виведення керує потоком зовнішніх даних. Мікроконтролери, в основному, мають архітектуру фон Неймана.
6
орієнтовані на архітектуру. Потім з'явилися компілятори з таких мов, як Fortran, Algol-68. Вони були орієнтовані на великі ЕОМ з пакетною обробкою задач. З перерахованих вище тільки Fortran продовжує використовуватися донині, оскільки має величезну кількість бібліотек різного призначення. Багато мов, народившись, так і не одержали широкого поширення — АDА, Моdula, Simula відомі лише вузькому колу фахівців. У той же час на ринку програмних систем домінують компілятори мов, яким не прочили світлого майбутнього. У першу чергу, зараз це С и С++. Перша з них народилася разом з операційними системами типу Unix, разом з нею завоювала своє «місце під сонцем», а потім перейшла під ОС інших типів. Друга втілила у собі приклад реалізації ідей об’ектно-орінтованого програмування. Ще можна згадати досить розповсюджений Раscal, що зненацька для багатьох вийшов за рамки чисто навчальної мови для університетського середовища.
Історія інтерпретаторів не настільки багата. Спочатку їм не віддавали істотного значення, оскільки майже по всіх параметрах вони уступають компіляторам. З відомих мов, що припускали інтерпретацію, можна згадати хіба що Ваsic.
Зараз ситуація змінилася, оскільки питання про переміщуваність програм і їх апаратно-платформної незалежності здобуває усе більшу актуальність з розвитком мережі Інтернет. Найвідоміший зараз приклад — це мова JаVа (сама по собі він сполучає компіляцію й інтерпретацію). Зрештою, мова НТМ, на якому ґрунтується протокол НТТР, що дав поштовх настільки бурхливому розвитку Всесвітньої мережі, — це теж интерпретуєма мова.
Вобласті появи нових інтерпретаторів усіх ще чекають сюрпризи, і з'явилися уже перші з них — наприклад, мова С# («сі-діез», але назва скрізь йде як «Си шарп»), яка анонсується фірмою Мicrosoft.
Етапи трансляції. Загальна схема роботи транслятора
На мал. 13.1 представлена загальна схема роботи компілятора. З неї видно, що в цілому процес компіляції складається з двох основних етапів
— синтезу й аналізу.
На етапі аналізу виконується розпізнавання тексту вихідної програми, створення і заповнення таблиць ідентифікаторів. Результатом його роботи служить деяке внутрішнє представлення програми, зрозуміле компілятору.
На етапі синтезу на підставі внутрішнього представлення програми й інформації, що міститься в таблиці (таблицях) ідентифікаторів,

7
породжується текст результуючої програми. Результатом цього етапу є об'єктний код.
Рис. 1. Загальна схема роботи компілятора
Крім того, у складі компілятора присутня частина, відповідальна за аналіз і виправлення помилок, яка при наявності помилки в тексті вихідної програми повинна максимально повно інформувати користувача про тип помилки і місце її виникнення. У кращому випадку компілятор може запропонувати користувачу варіант виправлення помилки.
Ці етапи, у свою чергу, складаються з більш дрібних етапів, названих фазами компіляції. Склад фаз компіляції приведений у самому загальному виді, їхня конкретна реалізація і процес взаємодії можуть, звичайно, розрізнятися в залежності від версії компілятора. Однак у тому чи іншому
8
виді всі представлені фази практично завжди присутні в кожному конкретному компіляторі.
Компілятор з погляду теорії формальних мов виконує дві основні функції.
По-перше, він розпізнає мову початкової програми. Тобто він повинний одержати на вхід ланцюжок символів вхідної мови, перевірити її належність мові і виявити правила, по яких цей ланцюжок був побудований.
По-друге, компілятор є генератором для мови результуючої програми. Він повинний побудувати на виході ланцюжок вихідної мови за визначеними правилами, передбачуваним мовою машинних команд чи мовою ассемблера. Розпізнувачем цього ланцюжка буде виступати вже обчислювальна система, під якою створюється результуюча програма.
Далі дається перелік основних фаз (частин) компіляції і короткий опис їх функцій. Більш докладна інформація дана в підрозділах, що відповідають цим фазам.
Лексичний аналіз (сканер) — це частина компілятора, що читає літери програми початковою мовою і будує з них слова (лексеми) початкової мови. На вхід лексичного аналізатора надходить текст початкової програми, а вихідна інформація передається для подальшої обробки компілятором на етапі синтаксичного розбору. З теоретичної точки зору лексичний аналізатор не є обов'язково необхідною частиною компілятора. Однак існують причини, що визначають його присутність практично у всіх компіляторах.
Синтаксичний розбір — це основна частина компілятора на етапі аналізу. Вона виконує виділення синтаксичних конструкцій у тексті вихідної програми, обробленому лексичним аналізатором. На цій же фазі компіляції перевіряється синтаксична правильність програми. Синтаксичний розбір відіграє головну роль — роль розпізнавача тексту вхідної мови програмування.
Семантичний аналіз — це частина компілятора, що перевіряє правильність тексту вихідної програми з погляду семантики вхідної мови. Крім безпосередньо перевірки, семантичний аналіз повинний виконувати перетворення тексту, які вимагаються семантикою вхідної мови (такі, як додавання функцій неявного перетворення типів). У різних реалізаціях компіляторів семантичний аналіз може частково входити у фазу синтаксичного розбору, частково — у фазу підготовки до генерації коду.
Підготовка до генерації коду — це фаза, на якій компілятором виконуються попередні дії, безпосередньо зв'язані із синтезом тексту результуючої програми, але які ще не ведуть до породження тексту вихідною мовою. Звичайно в цю фазу входять дії, зв'язані з ідентифікацією елементів мови, розподілом пам'яті і т.п.
9
Генерація коду — це фаза, безпосередньо зв'язана з породженням команд, що складають речення вихідної мови й у цілому текст результуючої програми. Це основна фаза на етапі синтезу результуючої програми.
Крім безпосереднього породження тексту результуючої програми, генерація звичайно містить у собі також оптимізацію — процес, зв'язаний з обробкою уже породженого тексту. Іноді оптимізацію виділяють в окрему фазу компіляції, тому що вона впливає на якість і ефективність результуючої програми.
Таблиці ідентифікаторів (іноді — «таблиці символів») — це спеціальним образом організовані набори даних, які служать для збереження інформації про елементи початкової програми, що потім використовуються для породження тексту результуючої програми. Таблиця ідентифікаторів у конкретній реалізації компілятора може бути одна, або таких таблиць може бути кілька. Елементами початкової програми, інформацію про які потрібно зберігати в процесі компіляції, є перемінні, константи, функції і т.п. - конкретний склад набору елементів залежить від використовуваної вхідної мови програмування. Поняття «таблиці» зовсім не припускає, що це сховище даних повинне бути організоване саме у виді таблиць або інших масивів інформації.
Розглянемо загальні аспекти технічної організації представлених фаз компіляції.
По-перше, на фазі лексичного аналізу лексеми виділяються з тексту вхідної програми остільки, оскільки вони необхідні для наступної фази синтаксичного розбору.
По-друге, синтаксичний розбір і генерація коду можуть виконуватися одночасно. Таким чином, ці три фази компіляції можуть працювати комбіновано, а разом з ними може виконуватися і підготовка до генерації коду.
Далі розглянуті технічні питання реалізації основних фаз компіляції, що тісно зв'язані з поняттям проходу.
Поняття проходу. Багатохідні і однопрохідні компілятори
Процес компіляції програм складається з декількох фаз.
У реальних компіляторах склад цих фаз може відрізнятися від розглянутого — деякі з них можуть бути розбиті на складові, інші, навпроти, об'єднані в одну фазу. Порядок виконання фаз компіляції також може мінятися в різних варіантах компіляторів. В одному випадку компілятор переглядає текст початкової програми, відразу виконує усі фази компіляції й одержує результат — об'єктний код. В іншому варіанті він виконує над вихідним текстом тільки деякі з фаз компіляції й одержує
10
не кінцевий результат, а набір проміжних даних. Ці дані потім знову обробляються. І цей процес може повторюватися декілька разів.
Реальні компілятори, як правило виконують трансляцію тексту початкової програми за декілька проходів.
Прохід - це процес послідовного читання компілятором даних з зовнішньої пам’яті, їх обробки і розміщення результату у зовнішню пам’ять.
Частіше всього один прохід включає в себе виконання одної або декількох фаз компіляції. Результатом проміжних проходів є внутрішнє представлення початкової програми, а результатом останнього проходу – результуюча об’єктна програма.
Вякості зовнішньої пам’яті можуть виступати любі носії інформації
–оперативна пам’ять комп’ютера, накопичувачі на магнітних дисках,
магнітні стрічки і т.д. Сучасні компілятори використовують для зберігання оперативну пам’ять і тільки у випадку її нестачі використовують накопичувачі на жорстких магнітних дисках.
При виконанні кожного проходу компілятору доступна інформація, яка була одержана в результаті всіх попередніх проходів. Як правило, він використовує інформацію, одержану при останньому проході, але при необхідності може звертатися і до даних попередніх проходів аж до початкової програми. Інформація, яка отримується компілятором при виконанні проходів, недоступна користувачу. Вона або зберігається в ОП, або в тимчасових файлах на диску, які знищуються після завершення роботи компілятора. Тому людина, яка працює з компілятором не знає скільки він зробив проходів. Але кількість виконуваних проходів – важлива технічна характеристика компілятора. Солідні фірми – розробники компіляторів звичайно вказують це в описі свого продукту.
При зменшенні кількості проходів збільшується швидкість роботи компілятора і зменшується об’єм пам’яті, яка йому необхідна. Однопрохідний компілятор, яких на вхід отримує початкову програму, і який одразу породжує результуючу об’єктну програму – це ідеальний варіант.
Але скоротити кількість проходів не завжди вдається. Вона визначається граматикою і семантикою початкової мови. Чим складніше граматика і чим більше варіантів пропонують семантичні правила, тим більше проходів буде виконувати компілятор. Саме тому компілятори з мови Паскаль працюють швидше за компілятори з мови С – граматика мови Паскаль більш проста, а семантичні правила більш жорсткі.
Реальні компілятори виконують від двох до п’яти проходів. Таким чином реальні компілятори є багатопрохідними. Найбільш розповсюдженими є двохта трьохпрохідні компілятори. Тоді перший прохід – це лексичний аналіз, другий – синтаксичний розбір і семантичний аналіз, третій – генерація коду.