Языки ассемблера
Макропроцессор, кроме раскрытия макросов, обычно предоставляет также директивы условной компиляции – в зависимости от условий, те или иные участки кода могут передаваться компилятору или нет. Условия, конечно же, должны быть известны уже на этапе компиляции. Например, в зависимости от типа целевого процессора одна и та же конструкция может реализоваться как в одну команду, так и эмулирующей программой. В зависимости от используемой операционной системы могут применяться разные системные вызовы (это чаще случается при программировании на языках высокого уровня), или в зависимости от значений параметров макроопределения, макрос может порождать совсем разный код.
Макросредства есть не только в ассемблерах, но и во многих языках высокого уровня (ЯВУ). Наиболее известен препроцессор языка С. В действительности, многие средства, предоставляемые языками, претендующими на большую, чем у С, "высокоуровневость" (что бы под этим ни подразумевалось), также реализуются по принципу макрообработки, т. е. при помощи текстовых подстановок и компиляции результата: шаблоны (template) C++, параметризованные типы Ada и т. д.
Умелое использование макропроцессора облегчает чтение кода и увеличивает возможности его повторного использования в различных ситуациях. Злоупотребление же макросредствами (как, впрочем, и многими другими мощными и выразительными языковыми конструкциями) или просто бестолковое их применение может приводить к совершенно непонятному коду и трудно диагностируемым ошибкам, поэтому многие теоретики программирования выступали за полный отказ от использования макропроцессоров.
Современные методы оптимизации в языках высокого уровня – проверка константных условий, разворачивание циклов, inlme-функции – часто стирают различия между макрообработкой и собственно компиляцией.
Кроме избавления программиста от необходимости запоминать коды команд, ассемблер выполняет еще одну, пожалуй, даже более важную функцию: он позволяет снабжать символическими именами (метками) или (символами) команды или ячейки памяти, предназначенные для данных. Значение этой возможности для практического программирования трудно переоценить.
Рассмотрим простой пример из жизни: мы написали программу, которая содержит команду перехода (бывают и программы, которые ни одной команды перехода не содержат, но это вырожденный случай). Затем, в процессе тестирования этой программы или уточнения спецификаций мы поняли, что между командой перехода и точкой, в которую переход совершается, необходимо вставить еще два десятка команд. Для вставки необходимо пересчитать адрес перехода. На практике, вставка даже одной только инструкции часто затрагивает и приводит к необходимости пересчитывать адреса множества команд перехода, поэтому возможность автоматизировать этот процесс крайне важна.
Важное применение меток – организация ссылок между модулями в программах, собираемых из нескольких раздельно компилируемых файлов. Изменение объема кода или данных в любом из модулей приводит к необходимости пересчета адресов во всех остальных модулях. В современных программах, собираемых из сотен отдельных файлов и содержащих тысячи индивидуально адресуемых объектов, выполнять такой пересчет вручную невозможно. Способы автоматического решения этой задачи обсуждаются в разд. "Сборка программ".
Фаза сопоставления символов с реальными адресами присутствует и при компиляции языков высокого уровня – компилятор генерирует символы не только для переменных, процедур и меток, которые могут быть использованы в операторе goto, но и для реализации "структурных" условных операторов и циклов. Нередко в описании компилятора эту фазу так и называют – ассемблирование.
Многие компиляторы как старые, так и современные, например, популярный компилятор GNU С, даже не выполняют фазу ассемблирования самостоятельно, а вместо этого генерируют текст на языке ассемблера и вызывают внешний ассемблер. Средства межпроцессного взаимодействия современных ОС позволяют передавать этот промежуточный текст, не создавая промежуточного файла, поэтому для конечного пользователя эта деталь реализации часто оказывается незаметной.
Компиляторы, имеющие встроенный ассемблер, такие, как Microsoft C/C++ или Watcom, часто могут генерировать ассемблерное представление порождаемого кода. Это бывает полезно при отладке или написании подпрограмм на ассемблере, которые должны взаимодействовать с откомпилированным кодом.