Есть ли реальная разница между компилятором и ассемблером?

15

Есть ли разница между ними? Согласно книге Уллмана , компиляторы конвертируют один язык в другой (обычно низкоуровневый), как и ассемблер. Чем они отличаются?

gpuguy
источник
1
Ассемблер - это компилятор, который выполняет определенный набор задач. Термины несколько разошлись на практике, но применяется базовое определение «компилятор» (перевод между языками).
Рафаэль
Все ассемблеры являются (простыми) компиляторами, поскольку они преобразуют один язык в другой. Не все компиляторы являются сборщиками.
user253751

Ответы:

16

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

Юваль Фильмус
источник
2
Почему перевод можно сделать только одним способом? Означает ли это, что исходный асм-код не может быть сгенерирован для данного машинного кода (и целевой архитектуры)? Это звучит противоречиво для меня. Потому что, если заданная инструкция машинного кода может отображаться на несколько инструкций asm, то как машина решает, какую инструкцию выполнить? Я что-то пропустил?
Утку
3
AT(A)A
Спасибо, я также вижу, что в книге Уллмана компилятор имеет внешний и внутренний интерфейсы. Если я прав, серверная часть выполняет оптимизацию на промежуточном языке, генерацию машинного кода и оптимизацию машинного кода, и каждая из трех задач может быть выполнена несколькими способами. «Бэкэнд» - это ассемблер? Является ли промежуточный язык ассемблером? Думаю, да, но ваш ответ упоминает, что ассемблер выполняет свою работу только одним способом.
Тим
Я разместил это здесь cs.stackexchange.com/questions/98854/…
Тим
Промежуточный язык обычно относится к языку, который не зависит от машины.
Юваль Фильмус
11

Суть в том, что писать компилятор веселее, чем ассемблером. Языки ассемблера обычно разрабатываются так, чтобы выполнять синтаксический анализ и проверку типов почти тривиально, и, как правило, включают в себя множество генераторов, управляемых таблицами («код операции для добавления - 01110», «для инструкций загрузки регистр операнда назначения указывается битами с 17 по 21»). «). Обычно наиболее интересной частью ассемблера является часть, которая преобразует символические метки в числа.

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

Ассемблер MIPS должен был сделать шаг вперед, принять несколько интересных решений по генерации кода и провести небольшую оптимизацию. Например, для машинного языка MIPS требуются разные кодовые последовательности для загрузки разных констант, и поэтому ассемблеру пришлось выбирать кодовую последовательность после построения константы . Кроме того, машинный код MIPS имел понятие слотов задержки , но ассемблер обязан был абстрагировать их и представить компилятору более «нормальный» абстрактный язык ассемблера. Таким образом, ассемблеру MIPS необходимо выполнить локальное планирование команд.

Это различие еще более размыто некоторыми работами Нормана Рэмси , в частности его C- переносимым языком ассемблера. (Соответствующий документ - Рэмси и Пейтон Джонс, «Единый промежуточный язык, который поддерживает множественные реализации исключений», Prog. Lang. Impl. И Dsgn. , (PLDI-21): 285–298, 2000. ) И, наконец, есть Это также Typed Assembly Language от Дэвида Уокера и Грега Моррисетта с ассемблером, который может гарантировать безопасность памяти.

Блуждающая логика
источник
0

Здесь немного упрощенный ответ, реальность сложнее. Я ожидаю, что разница между Ассемблером (A) и Компилятором (C) будет среди прочего:

  1. Одна строка исходного кода относится непосредственно к одному операционному коду процессора (A) или нет (C)
  2. Сильно зависит от фактического процессора (A) или независимой от компьютера (C)

Мы обычно называем язык ассемблера «низкий уровень», а исходный язык компилятор понимает как «высокий уровень» (это грубое упрощение, но все же).

Например, на ассемблере вы можете выполнить операцию добавления, сказав:

  • добавить a, b (для одного конкретного процессора)
  • добавить R5, R6 (для другого процессора)
  • добавить (A5), D2 (для другого процессора)

На языке высокого уровня вы можете написать:

  • х = у + z;

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

Как вы можете видеть, исходный язык ассемблера наиболее часто встречается: (A) одна строка исходного кода дает одну строку кодов операций CPU, и это очень сильно зависит от того, на какой процессор вы ориентируетесь. Компилятор языка высокого уровня (C) обрабатывает все эти детали за вас - одна строка исходного кода может стать нулевой, один или несколько кодов операций CPU, а компилятор обрабатывает детали того, что может делать CPU.

Компилятор сегодня часто состоит из нескольких разных этапов. Их можно назвать frontend / backend или другими именами. Я обычно вижу их в четыре этапа:

  1. Первый этап читает фактический исходный код и создает внутреннее представление. Этот этап знает фактический исходный язык.
  2. Второй этап рассматривает внутреннее представление и выполняет ряд оптимизаций. В настоящее время компилятор обычно стремится сделать программу быстрее и не заботиться о том, станет ли она больше. Оптимизация выполняется на внутреннем представлении. Интересно, что части этого могут быть общими для нескольких разных языков.
  3. Третий этап принимает внутреннее представление и создает фактический код для выбранного процессора. На этом этапе может быть несколько разных версий, предназначенных для разных процессоров. По сути, вы можете написать исходный код один раз, а затем скомпилировать его для разных CPUS.
  4. Окончательная подготовка к «упаковке» программы (на этом этапе может быть компоновщик).

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

ghellquist
источник