В программировании на компьютере язык ассемблера (альтернативно язык ассемблера [1] или символический машинный код ), [2] [3] [4] часто называемый просто ассемблером и обычно сокращенно обозначаемый как ASM или asm , представляет собой любой низкоуровневый язык программирования с очень строгим соответствием между инструкциями в языке и инструкциями машинного кода архитектуры . [5] Язык ассемблера обычно имеет один оператор на машинную инструкцию (1:1), но константы, комментарии , директивы ассемблера [6] символические метки , например, ячеек памяти , регистров и макросов [7] [1] обычно также поддерживаются.
Первый ассемблерный код, в котором язык используется для представления инструкций машинного кода, можно найти в работе Кэтлин и Эндрю Дональда Бута 1947 года «Кодирование для ARC» . [8] Ассемблерный код преобразуется в исполняемый машинный код с помощью служебной программы, называемой ассемблером . Термин «ассемблер» обычно приписывается Уилксу , Уиллеру и Гиллу в их книге 1951 года «Подготовка программ для электронного цифрового компьютера» , [9] которые, однако, использовали этот термин для обозначения «программы, которая собирает другую программу, состоящую из нескольких разделов, в одну программу». [10] Процесс преобразования называется сборкой , как и при сборке исходного кода . Вычислительный этап, когда ассемблер обрабатывает программу, называется временем сборки .
Поскольку сборка зависит от инструкций машинного кода, каждый язык ассемблера [примечание 1] специфичен для определенной компьютерной архитектуры . [11] [12] [13]
Иногда для одной и той же архитектуры существует более одного ассемблера, а иногда ассемблер специфичен для операционной системы или для конкретных операционных систем. Большинство языков ассемблера не предоставляют специального синтаксиса для вызовов операционной системы, и большинство языков ассемблера могут использоваться универсально с любой операционной системой, [примечание 2], поскольку язык обеспечивает доступ ко всем реальным возможностям процессора , на которых в конечном итоге покоятся все механизмы системных вызовов . В отличие от языков ассемблера, большинство языков программирования высокого уровня , как правило, переносимы между несколькими архитектурами, но требуют интерпретации или компиляции , гораздо более сложных задач, чем сборка.
В первые десятилетия вычислительной техники было обычным делом, когда как системное программирование , так и прикладное программирование полностью выполнялось на языке ассемблера. Хотя для некоторых целей он все еще незаменим, большая часть программирования теперь выполняется на интерпретируемых и компилируемых языках более высокого уровня. В « No Silver Bullet » Фред Брукс подытожил последствия перехода от программирования на языке ассемблера: «Безусловно, самым мощным ударом для производительности, надежности и простоты программного обеспечения стало прогрессивное использование языков высокого уровня для программирования. Большинство наблюдателей приписывают этому развитию по крайней мере пятикратный рост производительности и сопутствующий рост надежности, простоты и понятности». [14]
Сегодня типично использовать небольшие объемы кода на языке ассемблера в более крупных системах, реализованных на языке более высокого уровня, по соображениям производительности или для прямого взаимодействия с оборудованием способами, не поддерживаемыми языком более высокого уровня. Например, чуть менее 2% версии 4.9 исходного кода ядра Linux написано на ассемблере; более 97% написано на C. [15 ]
Язык ассемблера использует мнемонику для представления, например, каждой низкоуровневой машинной инструкции или кода операции , каждой директивы , как правило, также каждого архитектурного регистра , флага и т. д. Некоторые мнемоники могут быть встроенными, а некоторые — определяемыми пользователем. Многие операции требуют одного или нескольких операндов для формирования полной инструкции. Большинство ассемблеров допускают именованные константы, регистры и метки для программ и ячеек памяти и могут вычислять выражения для операндов. Таким образом, программисты освобождаются от утомительных повторяющихся вычислений, а программы на ассемблере гораздо более читабельны, чем машинный код. В зависимости от архитектуры эти элементы также могут быть объединены для определенных инструкций или режимов адресации с использованием смещений или других данных, а также фиксированных адресов. Многие ассемблеры предлагают дополнительные механизмы для облегчения разработки программ, управления процессом сборки и помощи в отладке .
Некоторые из них ориентированы на столбцы, с определенными полями в определенных столбцах; это было очень распространено для машин, использующих перфокарты в 1950-х и начале 1960-х годов. Некоторые ассемблеры имеют синтаксис свободной формы, с полями, разделенными разделителями, например, пунктуацией, пробелом . Некоторые ассемблеры являются гибридными, с, например, метками, в определенном столбце и другими полями, разделенными разделителями; это стало более распространенным, чем синтаксис, ориентированный на столбцы, в 1960-х годах.
Программа ассемблера создает объектный код , транслируя комбинации мнемоники и синтаксиса для операций и режимов адресации в их числовые эквиваленты. Это представление обычно включает код операции (« opcode »), а также другие управляющие биты и данные. Ассемблер также вычисляет константные выражения и разрешает символические имена для ячеек памяти и других сущностей. [20] Использование символических ссылок является ключевой особенностью ассемблеров, экономящей утомительные вычисления и ручные обновления адресов после изменений программы. Большинство ассемблеров также включают макровозможности для выполнения текстовой подстановки — например, для генерации общих коротких последовательностей инструкций как встроенных , вместо вызываемых подпрограмм .
Некоторые ассемблеры также могут выполнять некоторые простые типы оптимизаций , специфичных для набора инструкций . Одним из конкретных примеров этого могут быть вездесущие ассемблеры x86 от различных поставщиков. Называемые jump-sizing , [20] большинство из них способны выполнять замены jump-instruction (длинные переходы заменяются короткими или относительными переходами) в любом количестве проходов по запросу. Другие могут даже выполнять простую перестановку или вставку инструкций, например, некоторые ассемблеры для архитектур RISC , которые могут помочь оптимизировать разумное планирование инструкций для максимально эффективного использования конвейера ЦП . [21]
Ассемблеры были доступны с 1950-х годов, как первый шаг после машинного языка и до языков программирования высокого уровня, таких как Fortran , Algol , COBOL и Lisp . Также существовало несколько классов трансляторов и полуавтоматических генераторов кода со свойствами, аналогичными как ассемблеру, так и языкам высокого уровня, причем Speedcode, возможно, является одним из наиболее известных примеров.
Может быть несколько ассемблеров с различным синтаксисом для конкретного процессора или архитектуры набора инструкций . Например, инструкция по добавлению данных памяти в регистр в процессоре семейства x86 может быть add eax,[ebx]
в оригинальном синтаксисе Intel , тогда как это будет написано addl (%ebx),%eax
в синтаксисе AT&T, используемом GNU Assembler . Несмотря на разный внешний вид, различные синтаксические формы обычно генерируют один и тот же числовой машинный код . Один ассемблер может также иметь различные режимы для поддержки вариаций синтаксических форм, а также их точных семантических интерпретаций (таких как синтаксис FASM , синтаксис TASM , идеальный режим и т. д. в особом случае программирования ассемблера x86 ).
Существует два типа ассемблеров, в зависимости от того, сколько проходов по исходному коду необходимо (сколько раз ассемблер читает исходный код) для создания объектного файла.
В обоих случаях ассемблер должен иметь возможность определять размер каждой инструкции на начальных проходах, чтобы вычислять адреса последующих символов. Это означает, что если размер операции, ссылающейся на операнд, определенный позже, зависит от типа или расстояния операнда, ассемблер сделает пессимистическую оценку при первой встрече с операцией и, если необходимо, дополнит ее одной или несколькими инструкциями « no-operation » в более позднем проходе или исправлениях. В ассемблере с оптимизацией peephole адреса могут пересчитываться между проходами, чтобы позволить заменить пессимистический код кодом, адаптированным к точному расстоянию от цели.
Первоначальной причиной использования однопроходных ассемблеров был размер памяти и скорость сборки — часто второй проход требовал сохранения таблицы символов в памяти (для обработки прямых ссылок ), перемотки и повторного считывания исходного кода программы на ленте или повторного считывания колоды карт или перфоленты . Более поздние компьютеры с гораздо большей памятью (особенно дисковой) имели место для выполнения всей необходимой обработки без такого повторного считывания. Преимущество многопроходного ассемблера заключается в том, что отсутствие ошибок ускоряет процесс компоновки (или загрузку программы , если ассемблер напрямую создает исполняемый код). [22]
Пример: в следующем фрагменте кода однопроходный ассемблер сможет определить адрес обратной ссылки BKWD при сборке оператора S2 , но не сможет определить адрес прямой ссылки FWD при сборке оператора ветвления S1 ; действительно, FWD может быть неопределен. Двухпроходный ассемблер определит оба адреса в проходе 1, поэтому они будут известны при генерации кода в проходе 2.
S1 B ВПЕРЕД ...FWD EQU * ...BKWD EQU * ...S2 B BKWD
Более сложные высокоуровневые ассемблеры предоставляют такие языковые абстракции, как:
Более подробную информацию см. в разделе «Проектирование языка» ниже.
Программа, написанная на языке ассемблера, состоит из серии мнемонических инструкций процессора и мета-операторов (известных по-разному как декларативные операции, директивы, псевдоинструкции, псевдооперации и псевдооперации), комментариев и данных. Инструкции языка ассемблера обычно состоят из мнемоники кода операции , за которой следует операнд , который может быть списком данных, аргументов или параметров. [24] Некоторые инструкции могут быть «подразумеваемыми», что означает, что данные, с которыми работает инструкция, неявно определены самой инструкцией — такая инструкция не принимает операнд. Результирующий оператор транслируется ассемблером в инструкции машинного языка , которые могут быть загружены в память и выполнены.
Например, инструкция ниже сообщает процессору x86 / IA-32 о необходимости немедленного перемещения 8-битного значения в регистр . Двоичный код этой инструкции — 10110, за которым следует 3-битный идентификатор регистра, который следует использовать. Идентификатор регистра AL — 000, поэтому следующий машинный код загружает регистр AL данными 01100001. [24]
10110000 01100001
Этот двоичный компьютерный код можно сделать более понятным для человека, если выразить его в шестнадцатеричном виде следующим образом.
Б0 61
Здесь B0
означает "Переместить копию следующего значения в AL ", и 61
является шестнадцатеричным представлением значения 01100001, что равно 97 в десятичной системе . Язык ассемблера для семейства 8086 предоставляет мнемонику MOV (сокращение от move ) для таких инструкций, поэтому приведенный выше машинный код можно записать следующим образом на языке ассемблера, дополнив его пояснительным комментарием, если требуется, после точки с запятой. Это гораздо легче читать и запоминать.
MOV AL , 61h ; Загрузка AL с 97 десятичными (61 шестнадцатеричными)
В некоторых языках ассемблера (включая этот) одна и та же мнемоника, например MOV, может использоваться для семейства связанных инструкций для загрузки, копирования и перемещения данных, будь то непосредственные значения, значения в регистрах или ячейки памяти, на которые указывают значения в регистрах или непосредственные (т.е. прямые) адреса. Другие ассемблеры могут использовать отдельные мнемоники опкодов, например L для «переместить память в регистр», ST для «переместить регистр в память», LR для «переместить регистр в регистр», MVI для «переместить непосредственный операнд в память» и т.д.
Если одна и та же мнемоника используется для разных инструкций, это означает, что мнемоника соответствует нескольким разным двоичным кодам инструкций, за исключением данных (например, 61h
в этом примере), в зависимости от операндов, следующих за мнемоникой. Например, для процессоров x86/IA-32 синтаксис языка ассемблера Intel MOV AL, AH
представляет собой инструкцию, которая перемещает содержимое регистра AH в регистр AL . Шестнадцатеричная форма [nb 3] этой инструкции выглядит так:
88 Э0
Первый байт, 88h, идентифицирует перемещение между регистром размером в байт и другим регистром или памятью, а второй байт, E0h, кодируется (тремя битовыми полями), чтобы указать, что оба операнда являются регистрами, источником является AH , а местом назначения — AL .
В таком случае, когда одна и та же мнемоника может представлять более одной двоичной инструкции, ассемблер определяет, какую инструкцию сгенерировать, проверив операнды. В первом примере операнд 61h
является допустимой шестнадцатеричной числовой константой и не является допустимым именем регистра, поэтому B0
применима может быть только инструкция. Во втором примере операнд AH
является допустимым именем регистра и не является допустимой числовой константой (шестнадцатеричной, десятичной, восьмеричной или двоичной), поэтому 88
применима может быть только инструкция.
Языки ассемблера всегда разрабатываются таким образом, чтобы такое отсутствие двусмысленности повсеместно обеспечивалось их синтаксисом. Например, в языке ассемблера Intel x86 шестнадцатеричная константа должна начинаться с цифры, так что шестнадцатеричное число 'A' (равное десятичному десяти) будет записано как 0Ah
или 0AH
, а не AH
, специально для того, чтобы оно не могло показаться именем регистра AH . (То же правило также предотвращает двусмысленность с именами регистров BH , CH и DH , а также с любым определяемым пользователем символом, который заканчивается буквой H и в противном случае содержит только символы, которые являются шестнадцатеричными цифрами, например, слово "BEACH".)
Возвращаясь к исходному примеру, в то время как код операции x86 10110000 ( B0
) копирует 8-битное значение в регистр AL , 10110001 ( B1
) перемещает его в CL , а 10110010 ( B2
) делает это в DL . Ниже приведены примеры на языке ассемблера для них. [24]
MOV AL , 1h ; Загрузка AL с немедленным значением 1 MOV CL , 2h ; Загрузка CL с немедленным значением 2 MOV DL , 3h ; Загрузка DL с немедленным значением 3
Синтаксис MOV также может быть более сложным, как показывают следующие примеры. [25]
MOV EAX , [ EBX ] ; Переместить 4 байта из памяти по адресу, содержащемуся в EBX, в EAX MOV [ ESI + EAX ], CL ; Переместить содержимое CL в байт по адресу ESI+EAX MOV DS , DX ; Переместить содержимое DX в сегментный регистр DS
В каждом случае мнемоника MOV транслируется непосредственно в один из кодов операций 88-8C, 8E, A0-A3, B0-BF, C6 или C7 ассемблером, и программисту обычно не нужно знать или помнить, какой именно. [24]
Преобразование языка ассемблера в машинный код является работой ассемблера, а обратное может быть, по крайней мере, частично достигнуто дизассемблером . В отличие от языков высокого уровня , существует однозначное соответствие между многими простыми операторами ассемблера и инструкциями машинного языка. Однако в некоторых случаях ассемблер может предоставлять псевдоинструкции (по сути макросы), которые расширяются в несколько инструкций машинного языка для предоставления обычно необходимой функциональности. Например, для машины, в которой отсутствует инструкция «переход, если больше или равно», ассемблер может предоставлять псевдоинструкцию, которая расширяется до машинных инструкций «установить, если меньше» и «переход, если ноль (по результату инструкции «установить»)». Большинство полнофункциональных ассемблеров также предоставляют богатый макроязык (обсуждается ниже), который используется поставщиками и программистами для генерации более сложного кода и последовательностей данных. Поскольку информация о псевдоинструкциях и макросах, определенных в среде ассемблера, отсутствует в объектной программе, дизассемблер не может реконструировать вызовы макросов и псевдоинструкций, а может только дизассемблировать фактические машинные инструкции, которые ассемблер сгенерировал из этих абстрактных сущностей языка ассемблера. Аналогично, поскольку комментарии в исходном файле языка ассемблера игнорируются ассемблером и не оказывают никакого влияния на генерируемый им объектный код, дизассемблер всегда полностью не может восстановить исходные комментарии.
Каждая архитектура компьютера имеет свой собственный машинный язык. Компьютеры различаются по количеству и типу поддерживаемых ими операций, по разным размерам и количеству регистров, а также по представлению данных в хранилище. Хотя большинство компьютеров общего назначения способны выполнять по сути одну и ту же функциональность, способы, которыми они это делают, различаются; соответствующие языки ассемблера отражают эти различия.
Для одного набора инструкций может существовать несколько наборов мнемоник или синтаксиса языка ассемблера, обычно реализуемых в различных программах ассемблера. В этих случаях наиболее популярным обычно является тот, который поставляется производителем ЦП и используется в его документации.
Два примера ЦП, которые имеют два разных набора мнемоник, — это семейство Intel 8080 и Intel 8086/8088. Поскольку Intel заявила авторские права на мнемоники своего языка ассемблера (на каждой странице своей документации, опубликованной в 1970-х и начале 1980-х годов, по крайней мере), некоторые компании, которые независимо производили ЦП, совместимые с наборами инструкций Intel, изобрели свои собственные мнемоники. ЦП Zilog Z80 , усовершенствованный Intel 8080A , поддерживает все инструкции 8080A и многие другие; Zilog изобрел совершенно новый язык ассемблера не только для новых инструкций, но и для всех инструкций 8080A. Например, там, где Intel использует мнемоники MOV , MVI , LDA , STA , LXI , LDAX , STAX , LHLD и SHLD для различных инструкций по передаче данных, язык ассемблера Z80 использует мнемонику LD для всех них. Аналогичный случай — процессоры NEC V20 и V30 , улучшенные копии Intel 8086 и 8088 соответственно. Как и Zilog с Z80, NEC изобрела новую мнемонику для всех инструкций 8086 и 8088, чтобы избежать обвинений в нарушении авторских прав Intel. (Сомнительно, могут ли такие авторские права быть действительными, и более поздние компании-производители процессоров, такие как AMD [nb 4] и Cyrix, переиздали мнемонику инструкций Intel x86/IA-32 без какого-либо разрешения или юридического наказания.) Сомнительно, что на практике многие люди, программировавшие V20 и V30, действительно писали на языке ассемблера NEC, а не Intel; поскольку любые два языка ассемблера для одной и той же архитектуры набора инструкций изоморфны (в некотором роде как английский и Pig Latin ), нет необходимости использовать собственный опубликованный производителем язык ассемблера с продуктами этого производителя.
Существует большое разнообразие в том, как авторы ассемблеров классифицируют операторы и в номенклатуре, которую они используют. В частности, некоторые описывают все, что не является машинной мнемоникой или расширенной мнемоникой, как псевдооперацию (псевдо-оп). Типичный язык ассемблера состоит из 3 типов операторов инструкций, которые используются для определения операций программы:
Инструкции (операторы) на языке ассемблера, как правило, очень просты, в отличие от таковых в языках высокого уровня . Как правило, мнемоника — это символическое имя для одной исполняемой инструкции машинного языка ( кода операции ), и для каждой инструкции машинного языка определена как минимум одна мнемоника кода операции. Каждая инструкция обычно состоит из операции или кода операции плюс ноль или более операндов . Большинство инструкций ссылаются на одно значение или пару значений. Операнды могут быть непосредственными (значение, закодированное в самой инструкции), регистрами, указанными в инструкции или подразумеваемыми, или адресами данных, расположенных в другом месте хранилища. Это определяется базовой архитектурой процессора: ассемблер просто отражает, как работает эта архитектура. Расширенные мнемоники часто используются для указания комбинации кода операции с определенным операндом, например, ассемблеры System/360 используют B
в качестве расширенной мнемоники для BC
с маской 15 и NOP
(«NO OPeration» — ничего не делать для одного шага) для BC
с маской 0.
Расширенные мнемоники часто используются для поддержки специализированного использования инструкций, часто для целей, не очевидных из названия инструкции. Например, многие процессоры не имеют явной инструкции NOP, но имеют инструкции, которые могут быть использованы для этой цели. В процессорах 8086 инструкция используется для , причем является псевдокодом операции для кодирования инструкции . Некоторые дизассемблеры распознают это и декодируют инструкцию как . Аналогично, ассемблеры IBM для System/360 и System/370 используют расширенные мнемоники и для и с нулевыми масками. Для архитектуры SPARC они известны как синтетические инструкции . [26]xchg ax,ax
nop
nop
xchg ax,ax
xchg ax,ax
nop
NOP
NOPR
BC
BCR
Некоторые ассемблеры также поддерживают простые встроенные макро-инструкции, которые генерируют две или более машинных инструкций. Например, с некоторыми ассемблерами Z80 инструкция ld hl,bc
распознается как генерирующая, ld l,c
за которой следует ld h,b
. [27] Иногда их называют псевдо-опкодами .
Мнемоники — это произвольные символы; в 1985 году IEEE опубликовал стандарт 694 для единого набора мнемоник, которые должны использоваться всеми ассемблерами. С тех пор стандарт был отозван.
Существуют инструкции, используемые для определения элементов данных для хранения данных и переменных. Они определяют тип данных, длину и выравнивание данных. Эти инструкции также могут определять, доступны ли данные внешним программам (программам, собранным отдельно) или только программе, в которой определен раздел данных. Некоторые ассемблеры классифицируют их как псевдооперации.
Директивы сборки, также называемые псевдокодами операций, псевдооперациями или псевдооперациями, — это команды, данные ассемблеру, «предписывающие ему выполнять операции, отличные от инструкций сборки». [20] Директивы влияют на работу ассемблера и «могут влиять на объектный код, таблицу символов, файл листинга и значения внутренних параметров ассемблера». Иногда термин псевдокод операции зарезервирован для директив, которые генерируют объектный код, например, те, которые генерируют данные. [28]
Названия псевдоопераций часто начинаются с точки, чтобы отличать их от машинных инструкций. Псевдооперации могут сделать сборку программы зависимой от параметров, введенных программистом, так что одна программа может быть собрана разными способами, возможно, для разных приложений. Или псевдооперация может использоваться для управления представлением программы, чтобы сделать ее более простой для чтения и обслуживания. Другое распространенное использование псевдоопераций — резервирование областей хранения для данных времени выполнения и опциональная инициализация их содержимого известными значениями.
Символические ассемблеры позволяют программистам связывать произвольные имена ( метки или символы ) с ячейками памяти и различными константами. Обычно каждой константе и переменной дается имя, чтобы инструкции могли ссылаться на эти ячейки по имени, тем самым способствуя самодокументированию кода . В исполняемом коде имя каждой подпрограммы связано с ее точкой входа, поэтому любые вызовы подпрограммы могут использовать ее имя. Внутри подпрограмм назначениям GOTO даются метки. Некоторые ассемблеры поддерживают локальные символы , которые часто лексически отличаются от обычных символов (например, использование «10$» в качестве назначения GOTO).
Некоторые ассемблеры, такие как NASM , обеспечивают гибкое управление символами, позволяя программистам управлять различными пространствами имен , автоматически вычислять смещения в структурах данных и назначать метки, которые ссылаются на литеральные значения или результат простых вычислений, выполняемых ассемблером. Метки также могут использоваться для инициализации констант и переменных с перемещаемыми адресами.
Языки ассемблера, как и большинство других компьютерных языков, позволяют добавлять комментарии к исходному коду программы , которые будут игнорироваться во время сборки. Разумное комментирование необходимо в программах на языке ассемблера, поскольку значение и цель последовательности двоичных машинных инструкций может быть трудно определить. «Сырой» (незакомментированный) язык ассемблера, сгенерированный компиляторами или дизассемблерами, довольно трудно читать, когда необходимо внести изменения.
Многие ассемблеры поддерживают предопределенные макросы , а другие поддерживают определяемые программистом (и многократно переопределяемые) макросы, включающие последовательности текстовых строк, в которые встроены переменные и константы. Определение макроса чаще всего [nb 5] представляет собой смесь операторов ассемблера, например, директив, символических машинных инструкций и шаблонов для операторов ассемблера. Эта последовательность текстовых строк может включать коды операций или директивы. После определения макроса его имя может использоваться вместо мнемоники. Когда ассемблер обрабатывает такой оператор, он заменяет оператор текстовыми строками, связанными с этим макросом, а затем обрабатывает их так, как если бы они существовали в файле исходного кода (включая, в некоторых ассемблерах, расширение любых макросов, существующих в заменяющем тексте). Макросы в этом смысле относятся к автокодерам IBM 1950-х годов. [29]
Макроассемблеры обычно имеют директивы, например, для определения макросов, определения переменных, установки переменных в результат арифметического, логического или строкового выражения, итерации, условной генерации кода. Некоторые из этих директив могут быть ограничены для использования в определении макроса, например, MEXIT в HLASM , в то время как другие могут быть разрешены в открытом коде (вне определений макросов), например, AIF и COPY в HLASM.
В языке ассемблера термин «макрос» представляет собой более всеобъемлющее понятие, чем в некоторых других контекстах, таких как препроцессор в языке программирования C , где его директива #define обычно используется для создания коротких однострочных макросов. Инструкции макросов ассемблера, такие как макросы в PL/I и некоторых других языках, могут быть сами по себе длинными «программами», выполняемыми ассемблером путем интерпретации во время сборки.
Поскольку макросы могут иметь «короткие» имена, но расширяться до нескольких или даже многих строк кода, их можно использовать для того, чтобы программы на языке ассемблера казались намного короче, требуя меньше строк исходного кода, как в случае с языками более высокого уровня. Их также можно использовать для добавления более высоких уровней структуры в программы ассемблера, опционально вводить встроенный отладочный код через параметры и другие подобные функции.
Макроассемблеры часто позволяют макросам принимать параметры . Некоторые ассемблеры включают довольно сложные макроязыки, включающие такие элементы языка высокого уровня, как необязательные параметры, символьные переменные, условные операторы, манипуляции со строками и арифметические операции, которые можно использовать во время выполнения данного макроса, и позволяющие макросам сохранять контекст или обмениваться информацией. Таким образом, макрос может генерировать многочисленные инструкции языка ассемблера или определения данных на основе аргументов макроса. Это может использоваться для генерации структур данных в стиле записей или « развернутых » циклов, например, или может генерировать целые алгоритмы на основе сложных параметров. Например, макрос «сортировки» может принимать спецификацию сложного ключа сортировки и генерировать код, созданный для этого конкретного ключа, не нуждаясь в тестах времени выполнения, которые потребовались бы для общей процедуры, интерпретирующей спецификацию. Организация, использующая язык ассемблера, который был значительно расширен с помощью такого набора макросов, может считаться работающей на языке более высокого уровня, поскольку такие программисты не работают с концептуальными элементами самого низкого уровня компьютера. Подчеркивая этот момент, макросы использовались для реализации ранней виртуальной машины в SNOBOL4 (1967), которая была написана на языке SNOBOL Implementation Language (SIL), языке ассемблера для виртуальной машины. Целевая машина переводила это в свой собственный код с помощью макроассемблера . [ 30] Это обеспечивало высокую степень переносимости для того времени.
Макросы использовались для настройки крупномасштабных программных систем для конкретных клиентов в эпоху мэйнфреймов, а также использовались персоналом клиентов для удовлетворения потребностей своих работодателей путем создания определенных версий операционных систем производителей. Это делалось, например, системными программистами, работающими с IBM Conversational Monitor System / Virtual Machine ( VM/CMS ) и с дополнениями IBM "обработки транзакций в реальном времени", Customer Information Control System CICS и ACP / TPF , системой для авиакомпаний/финансов, которая появилась в 1970-х годах и до сих пор управляет многими крупными системами компьютерного бронирования (CRS) и системами кредитных карт.
Также возможно использовать исключительно возможности обработки макросов ассемблера для генерации кода, написанного на совершенно разных языках, например, для генерации версии программы на COBOL с использованием чистой программы макроассемблера, содержащей строки кода COBOL внутри операторов времени ассемблера, инструктирующих ассемблер о генерации произвольного кода. IBM OS/360 использует макросы для выполнения генерации системы . Пользователь указывает параметры, кодируя серию макросов ассемблера. Сборка этих макросов генерирует поток заданий для сборки системы, включая язык управления заданиями и операторы управления утилитами .
Это потому, что, как было осознано в 1960-х годах, концепция «макрообработки» независима от концепции «ассемблирования», первая из которых в современных терминах больше является обработкой текста, обработкой текста, чем генерацией объектного кода. Концепция макрообработки появилась и появляется в языке программирования C, который поддерживает «инструкции препроцессора» для установки переменных и выполнения условных проверок их значений. В отличие от некоторых предыдущих макропроцессоров внутри ассемблеров, препроцессор C не является полным по Тьюрингу, поскольку ему не хватает возможности зацикливаться или «переходить», последнее позволяет программам зацикливаться.
Несмотря на мощь макрообработки, она вышла из употребления во многих языках высокого уровня (за исключением C , C++ и PL/I), оставаясь при этом неизменной для ассемблеров.
Замена параметров макроса производится строго по имени: во время обработки макроса значение параметра текстуально заменяется на его имя. Наиболее известным классом ошибок, возникших в результате, было использование параметра, который сам по себе был выражением, а не простым именем, когда автор макроса ожидал имя. В макросе:
foo: макрос анагрузка а*б
Намерение состояло в том, что вызывающий должен был предоставить имя переменной, а «глобальная» переменная или константа b использовалась бы для умножения «a». Если foo вызывается с параметром a-c
, происходит макрорасширение load a-c*b
. Чтобы избежать любой возможной двусмысленности, пользователи макропроцессоров могут заключать в скобки формальные параметры внутри определений макросов, или вызывающие могут заключать в скобки входные параметры. [31]
Были написаны пакеты макросов, предоставляющие структурированные программные элементы для кодирования потока выполнения. Самый ранний пример этого подхода был в наборе макросов Concept-14 [32] , первоначально предложенном Харланом Миллсом (март 1970 г.) и реализованном Марвином Кесслером в Федеральном системном подразделении IBM, который предоставлял IF/ELSE/ENDIF и аналогичные блоки управления потоком для программ на ассемблере OS/360. Это был способ сократить или исключить использование операций GOTO в коде ассемблера, одного из основных факторов, вызывающих спагетти-код в языке ассемблера. Этот подход был широко принят в начале 1980-х годов (последние дни широкомасштабного использования языка ассемблера). Набор инструментов High Level Assembler Toolkit от IBM [33] включает такой пакет макросов.
Другой проект был A-Natural, [34] «потоковый» ассемблер для процессоров 8080/ Z80 от Whitesmiths Ltd. (разработчиков Unix -подобной операционной системы Idris , и того, что, как сообщалось, было первым коммерческим компилятором C ). Язык был классифицирован как ассемблер, потому что он работал с сырыми машинными элементами, такими как коды операций , регистры и ссылки на память; но он включал синтаксис выражений для указания порядка выполнения. Скобки и другие специальные символы, наряду с блочно-ориентированными структурными программными конструкциями, контролировали последовательность сгенерированных инструкций. A-natural был построен как объектный язык компилятора C, а не для ручного кодирования, но его логический синтаксис завоевал некоторых поклонников.
С момента спада в развитии крупномасштабных языков ассемблера не наблюдалось заметного спроса на более сложные ассемблеры. [35] Несмотря на это, они все еще разрабатываются и применяются в случаях, когда ограничения ресурсов или особенности архитектуры целевой системы не позволяют эффективно использовать языки более высокого уровня. [36]
Ассемблеры с мощным макродвижком позволяют осуществлять структурное программирование с помощью макросов, таких как макрос switch, поставляемый с пакетом Masm32 (этот код представляет собой законченную программу):
include \ masm32 \ include \ masm32rt.inc ; использовать библиотеку Masm32 .code demomain: REPEAT 20 switch rv ( nrandom , 9 ) ; сгенерировать число от 0 до 8 mov ecx , 7 case 0 print "case 0" case ecx ; в отличие от большинства других языков программирования, print "case 7" ; переключатель Masm32 допускает "переменные случаи" case 1 .. 3 .if eax == 1 print "case 1" .elseif eax == 2 print "case 2" .else print "cases 1 to 3: other" .endif case 4 , 6 , 8 print "cases 4, 6 or 8" default mov ebx , 19 ; print 20 stars .Repeat print "*" dec ebx .Until Sign? ; цикл до установки флага знака endsw print chr$ ( 13 , 10 ) ENDM exit end demomain
Когда был представлен компьютер с хранимой программой, программы писались в машинном коде и загружались в компьютер с перфоленты или напрямую в память с помощью консольных переключателей. [ требуется ссылка ] Кэтлин Бут «приписывают изобретение языка ассемблера» [37] [38] на основе теоретической работы, которую она начала в 1947 году, работая над ARC2 в Биркбеке, Лондонский университет, после консультаций Эндрю Бута (впоследствии ее мужа) с математиком Джоном фон Нейманом и физиком Германом Голдстайном в Институте перспективных исследований . [38] [39]
В конце 1948 года электронный автоматический калькулятор с задержкой хранения (EDSAC) имел ассемблер (названный «начальными заказами»), интегрированный в его программу начальной загрузки . Он использовал однобуквенную мнемонику, разработанную Дэвидом Уилером , которого IEEE Computer Society считает создателем первого «ассемблера». [20] [40] [41] Отчеты по EDSAC ввели термин «ассемблирование» для процесса объединения полей в командное слово. [42] SOAP ( Symbolic Optimal Assembly Program ) был языком ассемблера для компьютера IBM 650, написанным Стэном Поли в 1955 году. [43]
Языки ассемблера устранили большую часть подверженного ошибкам, утомительного и трудоемкого программирования первого поколения , необходимого для самых первых компьютеров, освободив программистов от такой скуки, как запоминание числовых кодов и вычисление адресов. Когда-то они широко использовались для всех видов программирования. К концу 1950-х годов их использование в значительной степени было вытеснено языками более высокого уровня в поисках повышения производительности программирования . [44] Сегодня язык ассемблера по-прежнему используется для прямой манипуляции оборудованием, доступа к специализированным инструкциям процессора или для решения критических проблем производительности. [45] Типичные области применения — драйверы устройств , низкоуровневые встроенные системы и системы реального времени (см. § Текущее использование).
Многочисленные программы были написаны полностью на языке ассемблера. Burroughs MCP (1961) был первым компьютером, для которого операционная система была разработана не полностью на языке ассемблера; он был написан на Executive Systems Problem Oriented Language (ESPOL), диалекте Algol. Многие коммерческие приложения также были написаны на языке ассемблера, включая большую часть программного обеспечения для мэйнфреймов IBM, разработанного крупными корпорациями. COBOL , FORTRAN и некоторые PL/I в конечном итоге вытеснили язык ассемблера, хотя ряд крупных организаций сохранили инфраструктуры приложений на языке ассемблера вплоть до 1990-х годов.
Язык ассемблера был основным языком разработки для 8-битных домашних компьютеров, таких как Apple II , Atari 8-битные компьютеры , ZX Spectrum и Commodore 64. Интерпретируемый BASIC на этих системах не обеспечивал максимальной скорости выполнения и полного использования возможностей для полного использования имеющегося оборудования. Язык ассемблера был выбором по умолчанию для программирования 8-битных консолей, таких как Atari 2600 и Nintendo Entertainment System .
Ключевое программное обеспечение для совместимых с IBM PC , такое как MS-DOS , Turbo Pascal и Lotus 1-2-3 , было написано на языке ассемблера. Поскольку скорость компьютеров росла экспоненциально, язык ассемблера стал инструментом для ускорения частей программ, таких как рендеринг Doom , а не доминирующим языком разработки. В 1990-х годах язык ассемблера использовался для максимизации производительности таких систем, как Sega Saturn , [46] и в качестве основного языка для аркадного оборудования, использующего интегрированный ЦП/ГП TMS34010, такого как Mortal Kombat и NBA Jam .
Ведутся споры о полезности и производительности языка ассемблера по сравнению с языками высокого уровня. [47]
Хотя язык ассемблера имеет определенные нишевые применения, где он важен (см. ниже), существуют и другие инструменты для оптимизации. [48]
По состоянию на июль 2017 года [обновлять]индекс популярности языков программирования TIOBE ставит язык ассемблера на 11 место, опережая, например, Visual Basic . [49] Ассемблер можно использовать для оптимизации скорости или оптимизации размера. В случае оптимизации скорости современные оптимизирующие компиляторы , как утверждается, [50] преобразуют языки высокого уровня в код, который может работать так же быстро, как рукописный ассемблер, несмотря на некоторые контрпримеры. [51] [52] [53] Сложность современных процессоров и подсистем памяти делает эффективную оптимизацию все более сложной для компиляторов и программистов ассемблера. [54] [55] Повышение производительности процессора привело к тому, что большинство ЦП простаивают большую часть времени, [56] с задержками, вызванными предсказуемыми узкими местами, такими как промахи кэша, операции ввода-вывода и подкачка , что делает скорость выполнения сырого кода не проблемой для многих программистов.
Существуют ситуации, в которых разработчики могут выбрать использование языка ассемблера:
Язык ассемблера по-прежнему преподается в большинстве программ по информатике и электронной инженерии . Хотя сегодня немногие программисты регулярно работают с языком ассемблера как с инструментом, базовые концепции остаются важными. Такие фундаментальные темы, как двоичная арифметика , распределение памяти , обработка стека , кодирование набора символов , обработка прерываний и проектирование компилятора , было бы трудно изучить подробно без понимания того, как компьютер работает на аппаратном уровне. Поскольку поведение компьютера в основном определяется его набором инструкций, логичным способом изучения таких концепций является изучение языка ассемблера. Большинство современных компьютеров имеют схожие наборы инструкций. Поэтому изучение одного языка ассемблера достаточно для изучения основных концепций, распознавания ситуаций, в которых использование языка ассемблера может быть уместным, и для того, чтобы увидеть, как можно создать эффективный исполняемый код из языков высокого уровня. [23]
Язык ассемблера также можно назвать символическим машинным кодом.
Программирование на языке ассемблера имеет те же преимущества, что и программирование на машинном языке, за исключением того, что оно проще.
Язык ассемблера (или ассемблер) — это компилируемый низкоуровневый компьютерный язык. Он зависит от процессора, поскольку он в основном транслирует мнемоники ассемблера непосредственно в команды, которые понимает конкретный процессор, на основе один к одному. Эти мнемоники ассемблера являются набором инструкций для этого процессора.
Язык ассемблера часто специфичен для конкретной компьютерной архитектуры, поэтому существует несколько типов языков ассемблера. ARM становится все более популярным языком ассемблера.
Используемый как метаассемблер, он позволяет пользователю разрабатывать собственные языки программирования и генерировать процессоры для таких языков с минимальными усилиями.
В отношении использования 1401 Autocoder при кодировании макрокоманд действуют следующие незначительные ограничения...
Неоригинальные идеи, содержащиеся в следующем тексте, были получены из ряда источников, ... Однако считается, что следует выразить признательность профессору Джону фон Нейману и доктору Герману Гольдштейну за множество плодотворных обсуждений ...
{{cite web}}
: CS1 maint: неподходящий URL ( ссылка )Всегда ведутся споры о применимости языка ассемблера в нашем современном мире программирования.
... изменения в конструкции, как правило, влияют на производительность больше, чем ... не следует переходить сразу к языку ассемблера, пока ...