Відповіді:
Інша причина для компіляторів виробляти збірку, а не правильний код машини:
add eax,2
його можна перекласти на 83 c0 02
або 66 83 c0 02
, залежно від останніх директив, таких як use16
.
Компілятор зазвичай конвертує код високого рівня безпосередньо в машинну мову, але він може бути побудований модульним способом, щоб один задній край випромінював машинний код, а другий код складання (наприклад, GCC). Фаза генерації коду створює "код", який є деяким внутрішнім представленням машинного коду, який потім повинен бути перетворений у такий придатний формат, як машина машини або збірний код.
Історично багато помітних компіляторів безпосередньо виводили машинний код. Однак у цьому є деякі труднощі. Як правило, комусь, хто намагається підтвердити правильність роботи компілятора, буде легше вивчити вихідний код збірки, ніж машинний код. Крім того, можна (і було історично загальноприйнятим) використовувати однопрохідний компілятор C або Pascal для створення файлу на мові збірки, який потім може бути оброблений за допомогою двопрохідного асемблера. Генеруючий код безпосередньо потребує використання компілятора C або Pascal з двома проходами або іншого використання компілятора з одним проходом з подальшими засобами зворотного виправлення адрес переходу вперед [якщо середовище виконання робить розмір запущеної програми доступним у нерухоме місце, компілятор може записати список патчів в кінці коду і мати стартовий код застосувати ці патчі під час виконання; такий підхід дозволить збільшити розмір виконуваного файлу приблизно на чотири байти на точку патча, але покращив би швидкість генерації програми].
Якщо метою є компілятор, який працює швидко, пряме генерування коду може працювати добре. Однак для більшості проектів вартість створення коду на мові асемблери та його складання насправді не є основною проблемою на сьогодні. Наявність компіляторів виробляти код у формі, яка може добре взаємодіяти з кодом, виданим іншими компіляторами, як правило, є достатньо великою перевагою для виправдання збільшення часу компіляції.
Навіть платформи, які використовують один і той же набір інструкцій, можуть мати різні формати файлів об'єктів, що переміщуються. Я можу подумати про "a.out" (ранній UNIX), OMF, MZ (MS-DOS EXE), NE (16-бітова Windows), COFF (UNIX System V), Mach-O (OS X і iOS) та ELF (Linux та інші), а також їхні варіанти, такі як XCOFF (AIX), ECOFF (SGI) та портативний виконуваний файл (PE) на основі COFF на 32-бітних Windows. Компілятору, який виробляє мову складання, не потрібно багато знати про формати файлів об'єктних файлів, що дозволяє асемблеру та лінкеру інкапсулювати ці знання в окремий процес.
Дивіться також Різниця між OMF та COFF щодо переповнення стека.
Зазвичай компілятори внутрішньо працюють із послідовностями інструкцій. Кожна інструкція буде представлена структурою даних, що представляє її ім'я операції, операнди тощо. Коли операнди є адресами, ці адреси зазвичай будуть символічними посиланнями, а не конкретними значеннями.
Виведення асемблера порівняно просте. Досить важливо взяти внутрішню структуру даних компіляторів і скинути її в текстовий файл у певному форматі. Вихід Assembler також досить легко читати, що корисно, коли вам потрібно перевірити, що робить компілятор.
Виведення бінарних об'єктних файлів значно більше роботи. Письменнику-компілятору необхідно знати, як закодовані всі інструкції (що може бути далеко не тривіальним у деяких CPUS), вони повинні перетворити деякі символьні посилання на відносні адреси лічильника програми та інші в певну форму метаданих у файлі бінарних об'єктів. . Їм потрібно виписати все у форматі, який є дуже специфічним для системи.
Так, ви абсолютно можете зробити компілятор, який може виводити бінарні об'єкти безпосередньо, не виписуючи асемблер як проміжний крок. Питання, як і стільки речей у розробці програмного забезпечення, полягає в тому, чи варто скорочення часу на компіляцію додаткових робіт з розробки та обслуговування.
Найбільше знайомий мені компілятор (freepascal) може виводити асемблер на всі платформи, але може виводити бінарні об'єкти безпосередньо на підмножину платформ.
Компілятор повинен мати можливість виробляти висновок асемблера на додаток до звичайного переміщуваного коду на користь програміста.
Одного разу я просто не знайшов помилку в програмі C, що працює на Unix System V на машині LSI-11. Начебто нічого не спрацювало. Нарешті, у відчаї я мав спроможний компілятор C виводити асемблерну версію його перекладу. Я нарешті знайшов помилку! Компілятор виділяв більше регістрів, ніж існувало в машині! (Компілятор виділив регістри R0 до R8 на машині з лише регістрами R0 до R7.) Мені вдалося обійти помилку в компіляторі, і моя програма працювала.
Ще однією перевагою наявності виводу асемблера є намагання використовувати «стандартні» бібліотеки, які використовують різні параметри протоколів передачі. Пізніше компілятори C дозволяють мені встановити протокол з параметром ("pascal" змусив би компілятор додати параметри в заданому порядку, на відміну від стандарту C зворотного порядку).
Ще одна перевага - це дозволяє програмісту побачити, яку жахливу роботу виконує його компілятор. Просте твердження C займає близько 44 машинних інструкцій. Значення завантажуються з пам'яті, а потім швидко відкидаються. тощо, тощо, тощо ...
Я особисто вважаю, що мати компілятор замість об'єкта, що пересувається, справді нерозумно. Складаючи програму, компілятор збирає багато інформації про вашу програму. Зазвичай вся інформація зберігається в тому, що називається таблицею символів. Після виведення коду асемблера він перекидає всю цю таблицю інформації. Потім асемблер вивчає виділений код і повторно збирає інформацію, яку вже мав компілятор. Однак асемблер нічого не знає про If твердження For заяви або Хоча твердження. Тож вся ця інформація відсутня. Тоді ассемблер виробляє об'єктний модуль, що переміщується, якого не робив компілятор.
Чому ???