Tooprogram.ru

Компьютерный справочник
1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Слово в ассемблере

Переменные и типы данных ассемблера

Макс Петровмай 2013

Пример объявления переменных в программе для MASM32 — сегмент .data :

Как видно из приведенного примера, переменные в ассемблере MASM32 определяются в сегменте .data программы при помощи конструкции:
[имя] тип значение,
где имя — необязательный параметр. Имя (идентификатор) переменной может содержать цифры, буквы латинского алфавита, символ подчеркивания в любом порядке, но начинаться должно обязательно с буквы. Длина имени переменной допускается в MASM32 одиннадцатой версии до 247 символов включительно. Если значение переменной при старте программы не определено, вместо числа или символьного значения после указания типа записывают знак вопроса.

Инициализирующие переменную символьные значения (строки символов) заключают в кавычки. Необходимо учитывать, что переменные длиной 2 и более байт инициализируются строкой с ее инверсией, в результате записи

var2 dword «asdf»

переменная var2 получит перевернутое значение «fdsa». Такой переворот строк происходит по следующей причине. Символ в памяти компьютера (в кодировке win1251) занимает 1 байт. Ассемблер видит символьную строку, как набор байт, и присваивает многобайтной переменной строковое значение последовательно, первый (левый) символ помещая в младший байт переменной, второй символ помещая во второй байт и т.д. При этом ассемблер считает многобайтную переменную, инициализированную строкой, все же числом. Поэтому на печать (на экран) байты-символы выводятся в обратном порядке — младший байт помещается справа, получается, что строка печатается задом-наперед.

Целочисленные переменные в MASM32 могут быть:
1-байтовые (8 бит или 1 символ), обозначаются byte или db ,
2-байтовые (16 бит или 2 символа), обозначаются word или dw ,
4-байтовые (32 бит или 4 символа), обозначаются dword или dd ,
6-байтовые (48 бит или 6 символов), обозначаются fword или df ,
8-байтовые (34 бит или 8 символов), обозначаются qword или dq ,
10-байтовые (80 бит или 10 символов), обозначаются tword или dt .

Переменные с плавающей точкой:
4-байтовые, обозначаются real4 ,
8-байтовые, обозначаются real8 ,
10-байтовые, обозначаются real10 .

При описании массива можно использовать директиву

где N — число повторений (duplicate) переменной в памяти; val — инициализирующее значение. Например, по директиве

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

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

dim1 byte «qwerty»

имена и значения элементов будут таковы:
dim1[0] = «q»
dim1[1] = «w»
dim1[2] = «e»
dim1[3] = «r»
dim1[4] = «t»
dim1[5] = «y».

Строго говоря, сегмент данных программы — это всегда байтовый массив (последовательность ячеек памяти). В ассемблере определение (описание) переменной в виде массива — это необязательная условность, не влияющая на расположение данных в памяти компьютера. С именами переменных в ассемблере можно поступать вольно, обращаться к любой переменной, как к массиву, получать значения одних переменных через идентификаторы других.

На рисунке показаны первые восемь байт сегмента данных из фрагмента программы, помещенного в начале этой статьи. Имеем шесть переменных, первые четыре (a, b, abc, a1b2) однобайтовые, пятая и шестая (d, e) — двухбайтовые. Мы можем вызывать значения однобайтных переменных по имени предшествующей им однобайтной переменной, считая ее имя именем массива, пусть даже такая предшествующая переменная и не была нами явно описана в сегменте данных программы, как массив:
a = a[0] = ?
b = a[1] = 0
abc = a[2] = 35
a1b2 = a[3] = 35;
аналогично и с двухбайтовыми (и выше) переменными:
e = d[1] = 0.

Можно получить значение каждого из байтов многобайтовой переменной через имя предшествующей однобайтовой переменной, в нашем примере
a[4] — это младший байт переменной d,
a[5] — это старший байт переменной d,
a[6] — это младший байт переменной e,
a[7] — это старший байт переменной e.

Типы данных в ассемблере

Данные – числа и закодированные символы, используемые в качестве операндов команд.
Основные типы данных в ассемблере

ТипДирективаКоличество байт
БайтDB1
СловоDW2
Двойное словоDD4
8 байтDQ8
10 байтDT10

Данные, обрабатываемые вычислительной машиной, можно разделить на 4 группы:

  • целочисленные;
  • вещественные.
  • символьные;
  • логические;
Целочисленные данные

Целые числа в ассемблере могут быть представлены в 1-байтной, 2-байтной, 4-байтной или 8-байтной форме. Целочисленные данные могут представляться в знаковой и беззнаковой форме.

Беззнаковые целые числа представляются в виде последовательности битов в диапазоне от 0 до 2 n -1, где n- количество занимаемых битов.


Знаковые целые числа представляются в диапазоне -2 n-1 … +2 n-1 -1. При этом старший бит данного отводится под знак числа (0 соответствует положительному числу, 1 – отрицательному).

Вещественные данные

Вещественные данные могут быть 4, 8 или 10-байтными и обрабатываются математическим сопроцессором.

Логические данные

Логические данные представляют собой бит информации и могут записываться в виде последовательности битов. Каждый бит может принимать значение 0 (ЛОЖЬ) или 1 (ИСТИНА). Логические данные могут начинаться с любой позиции в байте.

Символьные данные

Символьные данные задаются в кодах и имеют длину, как правило, 1 байт (для кодировки ASCII) или 2 байта (для кодировки Unicode) .

Числа в двоично-десятичном формате

В двоично-десятичном коде представляются беззнаковые целые числа, кодирующие цифры от 0 до 9. Числа в двоично-десятичном формате могут использоваться в одном из двух видов:

В неупакованном виде в каждом байте хранится одна цифра, размещенная в младшей половине байта (биты 3…0).

Упакованный вид допускает хранение двух десятичных цифр в одном байте, причем старшая половина байта отводится под старший разряд.

Числовые константы

Числовые константы используются для обозначения арифметических операндов и адресов памяти. Для числовых констант в Ассемблере могут использоваться следующие числовые форматы.

Десятичный формат – допускает использование десятичных цифр от 0 до 9 и обозначается последней буквой d, которую можно не указывать, например, 125 или 125d. Ассемблер сам преобразует значения в десятичном формате в объектный шестнадцатеричный код и записывает байты в обратной последовательности для реализации прямой адресации.

Шестнадцатеричный формат – допускает использование шестнадцатеричных цифр от 0 до F и обозначается последней буквой h, например 7Dh. Так как ассемблер полагает, что с буквы начинаются идентификаторы, то первым символом шестнадцатеричной константы должна быть цифра от 0 до 9. Например, 0Eh.

Двоичный формат – допускает использование цифр 0 и 1 и обозначается последней буквой b. Двоичный формат обычно используется для более четкого представления битовых значений в логических командах (AND, OR, XOR).

Восьмеричный формат – допускает использование цифр от 0 до 7 и обозначается последней буквой q или o, например, 253q.

Массивы и цепочки

Массивом называется последовательный набор однотипных данных, именованный одним идентификатором.

Цепочка — массив, имеющий фиксированный набор начальных значений.

Примеры инициализации цепочек

Каждая из записей выделяет десять последовательных 4-байтных ячеек памяти и записывает в них значения 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Идентификатор M1 определяет смещение начала этой области в сегменте данных . DATA .

Для инициализации всех элементов массива одинаковыми значениями используется оператор DUP :

Идентификатор Тип Размер DUP (Значение)

Идентификатор — имя массива;
Тип — определяет количество байт, занимаемое одним элементом;
Размер — константа, характеризующая количество элементов в массиве
Значение — начальное значение элементов.

описывает массив a из 20 элементов, начальные значения которых равны 0.

Если необходимо выделить память, но не инициализировать ее, в качестве поля Значение используется знак ?. Например,

Символьные строки

Символьные строки представляют собой набор символов для вывода на экран. Содержимое строки отмечается

  • одиночными кавычками », например, ‘строка’
  • двойными кавычками «», например «строка»

Символьная строка определяется только директивой DB , в которой указывается более одного символа в последовательности слева направо.

Символьная строка, предназначенная для корректного вывода, должна заканчиваться нуль-символом ‘’ с кодом, равным 0.

Для перевода строки могут использоваться символы

  • возврат каретки с кодом 13 (0Dh)
  • перевод строки с кодом 10 (0Ah).

Ассемблер. Переменные и Константы

Обновл. 12 Окт 2019 |

NASM предоставляет различные директивы определения для резервирования места для хранения переменных. В ассемблере директива определения используется для выделения дискового пространства. Она может использоваться для резервирования или инициализации одного/нескольких байт.

Хранения инициализированных данных

Синтаксис стейтмента выделения памяти для инициализированных данных следующий:

Где имя_переменной является идентификатором для каждого пространства для хранения. Ассемблер связывает значение смещения для каждого имени переменной, определённого в сегменте данных.

Есть 5 основных форм директивы определения:

Директива Цель Пространство для хранения
DBОпределяет ByteВыделяет 1 байт
DWОпределяет WordВыделяет 2 байта
DDОпределяет DoublewordВыделяет 4 байта
DQОпределяет QuadwordВыделяет 8 байт
DTОпределяет 10 Byte-овВыделяет 10 байт

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

Обратите внимание, что:

каждый байт символа хранится как его ASCII-значение в шестнадцатеричном формате;

каждое десятичное значение автоматически конвертируется в 16-битный двоичный эквивалент и сохраняется в виде шестнадцатеричного числа;

процессор использует прямой порядок байтов;

отрицательные числа конвертируются в форму «two’s complement»;

короткие и длинные числа типа с плавающей запятой представлены с использованием 32 или 64 бит, соответственно.

Следующая программа показывает использование директивы определения:

Результат выполнения программы выше:

Хранение неинициализированных данных

Директивы резервирования используются для резервирования пространства для неинициализированных данных. Директивы резервирования принимают один операнд, который определяет количество единиц пространства, которое будет зарезервировано. Каждая директива определения имеет связанную директиву резервирования.

Есть 5 основных форм директив резервирования:

Директива Цель
RESBРезервирует Byte
RESWРезервирует Word
RESDРезервирует Doubleword
RESQРезервирует Quadword
RESTРезервирует 10 Byte-ов

Несколько определений

Вы можете иметь несколько стейтментов определения данных в программе. Например:

Ассемблер выделяет смежную память для нескольких определений переменных.

Несколько инициализаций

Директива TIMES позволяет выполнить несколько инициализаций одного значения. Например, массив с именем marks длиной 9 может быть определён и инициализирован нулём следующим образом:

Директива TIMES полезна при определении массивов и таблиц. Следующая программа выводит на экран 9 звездочек:

Почему Ассемблер — это круто, но сложно

Есть высо­ко­уров­не­вые язы­ки — это те, где вы гово­ри­те if — else, print, echo, function и так далее. «Высо­кий уро­вень» озна­ча­ет, что вы гово­ри­те с ком­пью­те­ром более-менее чело­ве­че­ским язы­ком. Дру­гой чело­век может не понять, что имен­но у вас напи­са­но в коде, но он хотя бы смо­жет про­чи­тать сло­ва.

Но сам ком­пью­тер не пони­ма­ет чело­ве­че­ский язык. Ком­пью­тер — это реги­стры памя­ти, про­стые логи­че­ские опе­ра­ции, еди­ни­цы и нули. Поэто­му преж­де чем ваша про­грам­ма будет испол­не­на про­цес­со­ром, ей нужен пере­вод­чик — про­грам­ма, кото­рая пре­вра­тит высо­ко­уров­не­вый язык про­грам­ми­ро­ва­ния в низ­ко­уров­не­вый машин­ный код.

Ассем­блер — это соби­ра­тель­ное назва­ние язы­ков низ­ко­го уров­ня: код всё ещё пишет чело­век, но он уже гораз­до бли­же к прин­ци­пам рабо­ты ком­пью­те­ра, чем к прин­ци­пам мыш­ле­ния чело­ве­ка.

Вари­ан­тов Ассем­бле­ра доволь­но мно­го. Но так как все они рабо­та­ют по оди­на­ко­во­му прин­ци­пу и исполь­зу­ют (в основ­ном) оди­на­ко­вый син­так­сис, мы будем все подоб­ные язы­ки назы­вать общим сло­вом «Ассем­блер».

Как мыслит процессор

Что­бы понять, как рабо­та­ет Ассем­блер и поче­му он рабо­та­ет имен­но так, нам нуж­но немно­го разо­брать­ся с внут­рен­ним устрой­ством про­цес­со­ра.

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

Реги­стры быва­ют раз­но­го вида и назна­че­ния: одни слу­жат, что­бы хра­нить инфор­ма­цию; дру­гие сооб­ща­ют о состо­я­нии про­цес­со­ра; тре­тьи исполь­зу­ют­ся как нави­га­то­ры, что­бы про­цес­сор знал, куда идти даль­ше, и так далее. Подроб­нее — в рас­хло­пе ↓

Обще­го назна­че­ния. Это 8 реги­стров, каж­дый из кото­рых может хра­нить все­го 4 бай­та инфор­ма­ции. Такой регистр мож­но раз­де­лить на 2 или 4 части и рабо­тать с ними как с отдель­ны­ми ячей­ка­ми.

Ука­за­тель команд. В этом реги­стре хра­нит­ся толь­ко адрес сле­ду­ю­щей коман­ды, кото­рую дол­жен выпол­нить про­цес­сор. Вруч­ную его изме­нить нель­зя, но мож­но на него повли­ять раз­лич­ны­ми коман­да­ми пере­хо­дов и про­це­дур.

Регистр фла­гов. Флаг — какое-то свой­ство про­цес­со­ра. Напри­мер, если уста­нов­лен флаг пере­пол­не­ния, зна­чит про­цес­сор полу­чил в ито­ге такое чис­ло, кото­рое не поме­ща­ет­ся в нуж­ную ячей­ку памя­ти. Он туда кла­дёт то, что поме­ща­ет­ся, и ста­вит в этот флаг циф­ру 1. Она — сиг­нал про­грам­ми­сту, что что-то пошло не так.

Фла­гов в про­цес­со­ре мно­го, какие-то мож­но менять вруч­ную, и они будут вли­ять на вычис­ле­ния, а какие-то мож­но про­сто смот­реть и делать выво­ды. Фла­ги — как сиг­наль­ные лам­пы на пане­ли при­бо­ров в само­лё­те. Они что-то озна­ча­ют, но толь­ко само­лёт и пилот зна­ют, что имен­но.

Сег­мент­ные реги­стры. Нуж­ны были для того, что­бы рабо­тать с опе­ра­тив­ной памя­тью и полу­чать доступ к любой ячей­ке. Сей­час такие реги­стры име­ют по 32 бита, и это­го доста­точ­но, что­бы полу­чить 4 гига­бай­та опе­ра­тив­ки. Для про­грам­мы на Ассем­бле­ре это­го обыч­но хва­та­ет.

Так вот: всё, с чем рабо­та­ет Ассем­блер, — это коман­ды про­цес­со­ра, пере­мен­ные и реги­стры.

Здесь нет при­выч­ных типов дан­ных — у нас есть толь­ко бай­ты памя­ти, в кото­рых мож­но хра­нить что угод­но. Даже если вы поме­сти­те в ячей­ку какой-то сим­вол, а потом захо­ти­те рабо­тать с ним как с чис­лом — у вас полу­чит­ся. А вме­сто при­выч­ных цик­лов мож­но про­сто прыг­нуть в нуж­ное место кода.

Команды Ассемблера

Каж­дая коман­да Ассем­бле­ра — это коман­да для про­цес­со­ра. Не опе­ра­ци­он­ной систе­ме, не фай­ло­вой систе­ме, а имен­но про­цес­со­ру — то есть в самый низ­кий уро­вень, до кото­ро­го может дотя­нуть­ся про­грам­мист.

Любая коман­да на этом язы­ке выгля­дит так:

Мет­ка — это имя для фраг­мен­та кода. Напри­мер, вы хоти­те отдель­но поме­тить место, где начи­на­ет­ся рабо­та с жёст­ким дис­ком, что­бы было лег­че читать код. Ещё мет­ка нуж­на, что­бы в дру­гом участ­ке про­грам­мы мож­но было напи­сать её имя и сра­зу пере­прыг­нуть к нуж­но­му кус­ку кода.

Коман­да — слу­жеб­ное сло­во для про­цес­со­ра, кото­рое он дол­жен выпол­нить. Спе­ци­аль­ные ком­пи­ля­то­ры пере­во­дят такие коман­ды в машин­ный код. Это сде­ла­но для того, что­бы не запо­ми­нать сами машин­ные коман­ды, а исполь­зо­вать вме­сто них какие-то бук­вен­ные обо­зна­че­ния, кото­рые про­ще запом­нить. В этом, соб­ствен­но, и выра­жа­ет­ся чело­веч­ность Ассем­бле­ра: коман­ды в нём хотя бы отда­лён­но напо­ми­на­ют чело­ве­че­ские сло­ва.

Опе­ран­ды отве­ча­ют за то, что имен­но будут делать коман­ды: какие ячей­ки брать для вычис­ле­ний, куда поме­щать резуль­тат и что сде­лать с ним допол­ни­тель­но. Опе­ран­дом могут быть назва­ния реги­стров, ячей­ки памя­ти или слу­жеб­ные части команд.

Ком­мен­та­рий — это про­сто пояс­не­ние к коду. Его мож­но писать на любом язы­ке, и на выпол­не­ние про­грам­мы он не вли­я­ет. При­ме­ры команд:

mov eax, ebx ; Пересылаем значение регистра EBX в регистр EAX

mov x, 0 ; Записываем в переменную x значение 0

add eax, х ; Складываем значение регистра ЕАХ и переменной х, результат отправится в регистр ЕАХ

Здесь нет меток, пер­вы­ми идут коман­ды (mov или add), а за ними — опе­ран­ды и ком­мен­та­рии.

Пример: возвести число в куб

Если нам пона­до­бит­ся вычис­лить х³, где х зани­ма­ет ров­но один байт, то на Ассем­бле­ре это будет выгля­деть так.

Пер­вый вари­ант

mov al, x ; Пересылаем x в регистр AL

imul al ; Умножаем регистр AL на себя, AX = x * x

movsx bx, x ; Пересылаем x в регистр BX со знаковым расширением

imul bx ; Умножаем AX на BX. Результат разместится в DX:AX

Вто­рой вари­ант

mov al, x ; Пересылаем x в регистр AL

imul al ; Умножаем регистр AL на себя, AX = x * x

cwde ; Расширяем AX до EAX

movsx ebx, x ; Пересылаем x в регистр EBX со знаковым расширением

imul ebx ; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX

На любом высо­ко­уров­не­вом язы­ке воз­ве­сти чис­ло в куб мож­но одной стро­кой. Напри­мер:

x = Math.pow(x,3);
x := exp(ln(x) * 3);
на худой конец x = x*x*x.

Хит­рость в том, что когда каж­дая из этих строк будет све­де­на к машин­но­му коду, это­го кода может быть и 5 команд, и 10, и 50, и даже 100. Чего сто­ит вызов объ­ек­та Math и его мето­да pow: толь­ко на эту слу­жеб­ную опе­ра­цию (ещё до само­го воз­ве­де­ния в куб) может уйти несколь­ко сотен и даже тысяч машин­ных команд.

А на Ассем­бле­ре это гаран­ти­ро­ван­но пять команд. Ну, или как реа­ли­зу­е­те.

Почему это круто

Ассем­блер поз­во­ля­ет рабо­тать с про­цес­со­ром и памя­тью напря­мую — и делать это очень быст­ро. Дело в том, что в Ассем­бле­ре почти не тра­тит­ся зря про­цес­сор­ное вре­мя. Если про­цес­сор рабо­та­ет на часто­те 3 гига­гер­ца — а это при­мер­но 3 мил­ли­ар­да про­цес­сор­ных команд в секун­ду, — то очень хоро­ший код на Ассем­бле­ре будет выпол­нять при­мер­но 2,5 мил­ли­ар­да команд в секун­ду. Для срав­не­ния, JavaScript или Python выпол­нят в тыся­чу раз мень­ше команд за то же вре­мя.

Ещё про­грам­мы на Ассем­бле­ре зани­ма­ют очень мало места в памя­ти. Имен­но поэто­му на этом язы­ке пишут драй­ве­ры, кото­рые встра­и­ва­ют пря­мо в устрой­ства, или управ­ля­ю­щие про­грам­мы, кото­рые зани­ма­ют несколь­ко кило­байт. Напри­мер, про­грам­ма, кото­рая нахо­дит­ся в бре­ло­ке сиг­на­ли­за­ции и управ­ля­ет без­опас­но­стью всей маши­ны, зани­ма­ет все­го пару десят­ков кило­байт. А всё пото­му, что она напи­са­на для кон­крет­но­го про­цес­со­ра и исполь­зу­ет его воз­мож­но­сти на сто про­цен­тов.

Спра­вед­ли­во­сти ради отме­тим, что совре­мен­ные ком­пи­ля­то­ры С++ дают машин­ный код, близ­кий по быст­ро­дей­ствию к Ассем­бле­ру, но всё рав­но немно­го усту­па­ют ему.

Почему это сложно

Для того, что­бы писать про­грам­мы на Ассем­бле­ре, нуж­но очень любить крем­ний:

  • пони­мать архи­тек­ту­ру про­цес­со­ра;
  • знать устрой­ство желе­за, кото­рое рабо­та­ет с этим про­цес­со­ром;
  • знать все коман­ды, кото­рые отно­сят­ся имен­но к это­му типу про­цес­со­ров;
  • уметь рабо­тать с дан­ны­ми в побай­то­вом режи­ме (забудь­те о стро­ках и мас­си­вах, ведь ваш мак­си­мум — это одна бук­ва);
  • пони­мать, как в огра­ни­чен­ных усло­ви­ях реа­ли­зо­вать нуж­ную функ­ци­о­наль­ность.

Теперь добавь­те к это­му отсут­ствие боль­шин­ства при­выч­ных биб­лио­тек для рабо­ты с чем угод­но, слож­ность чте­ния тек­ста про­грам­мы, мед­лен­ную ско­рость раз­ра­бот­ки — и вы полу­чи­те пол­ное пред­став­ле­ние о про­грам­ми­ро­ва­нии на Ассем­бле­ре.

Для чего всё это

Ассем­блер неза­ме­ним в таких вещах:

  • драй­ве­ры;
  • про­грам­ми­ро­ва­ние мик­ро­кон­трол­ле­ров и встра­и­ва­е­мых про­цес­со­ров;
  • кус­ки опе­ра­ци­он­ных систем, где важ­но обес­пе­чить ско­рость рабо­ты;
  • анти­ви­ру­сы (и виру­сы).

На самом деле на Ассем­бле­ре мож­но даже запи­лить свой сайт с фору­мом, если у про­грам­ми­ста хва­та­ет ква­ли­фи­ка­ции. Но чаще все­го Ассем­блер исполь­зу­ют там, где даже ско­ро­сти и воз­мож­но­стей C++ недо­ста­точ­но.

Читать еще:  Работа в ассемблере
Ссылка на основную публикацию
Adblock
detector