Организация данных в ассемблере.
Прежде, чем переходить к рассмотрению вопроса что такое константа, массив, структура в ассемблере, поговорим о понятии абстракции.
Для упрощения написания кода необходимо преобразовать его в понятный для человека вид, желательно не в ущерб для машины. Для этого используют условности и обобщения — определённую степень абстракции. Один из простейших способов абстракции — разбивка кода и данных на части — блоки по определённым правилам и с определёнными особенностями. Затем эти блоки обзывают понятным для человека языком.
Первоначально программа дробиться на сегменты — наиболее глобальные блоки. В исходнике данной статьи вы столкнётесь с сегментами: .CODE (сегмент кода), .DATA (сегмент инициализированных данных), .DATA? (сегмент неинициализированных данных), .CONST (сегмент констант). Инициализированные данные — те, которым присвоено значение по умолчанию, не инициализированные — значение не присвоено, но память для данных выделена (реально заполнена символами «0» или «?» — в зависимости от компилятора), константы — постоянные, не изменяемые данные. Ещё один из сегментов — .STACK. Если сегменты в коде не указаны, структура программы будет создана согласно настройкам по умолчанию.
Как мы уже знаем, в программах типа .COM, с которыми мы непосредственно работаем, данные, код и стек находятся в одном и том же 16-битном сегменте (в отличие от программ типа .EXE). Не смотря на это, для демонстрации принципа «блочности» и особенностей написания исходного кода на ассемблере, мы «раздробили» исходник на условные части кода (.CODE) и данных (.DATA, .DATA?). TASM проигнорирует наше условное дробление и ругаться не будет.
Схематичная структура кода на ассемблере.
Схематично структура исходного кода программы .COM выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 | ;Стандартный вид программы типа .COM .model tiny ; for СОМ .code ; code segment start org 100h ; offset in memory = 100h (for COM) start: ; точка входа ... код ; (.code - сегмент уже определён выше) ... данные ... константы ... стек end start |
Но для удобства программиста можно написать и так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | ;Не стандартный вид программы типа .COM - кому-то покажется более удобным разбить сегменты на части .model tiny ; for СОМ .code ; code segment start org 100h ; offset in memory = 100h (for COM) start: ; точка входа ... код ; (.code - сегмент уже определён выше) .const ... константы .data ... данные .code ... код .data ... и снова данные .code ... и снова код .data ... .code ... .data? ... не инициализированные данные .code ... end start |
Опустимся по линии абстракции кода ниже. Мы уже знаем и можем назвать один из блоков кода, который имеет имя, параметры и возвращаемое значение. Этот «блок» может вызываться практически неограниченное число раз, будучи написанным один раз Вы уже догадались, что это функция.
В этой статье речь пойдёт о блоках данных. Мы рассмотрим, что такое константа, массив, структура в ассемблере, а также более редко встречающиеся: перечисление, объединение, запись с битовыми полями (запись).
Беглый обзор.
Пробежимся по понятиям в ознакомительных целях, более подробно рассмотрим вопрос потом, когда перейдём к 32 битному Windows программированию.
Мы рассчитываем, что вы сами будете изучать выкладываемый код в свободное время. Пользуйтесь созданными для Вас возможностями: DOS-1.rar . TASMED насторен и готов к работе. При внесении изменения в код не забываем жать F2 для сохранения. Если при сборке кода возникает непонятная ошибка (!) — не забываем удалять ранее сгенерированные фалы (с расширениями *.OBJ, *.CODE). Они, как и проекты находятся в папке D:\WORK\Ваша_директория… и будут появляться вновь и вновь при удачной сборке программы. Удалять можно не выходя из DOS: выходим из TASMED (ALT+X), удаляем файлики (в NC — F8) и снова запускаем TASMED — откроется сразу с кодом.
Константа в ассемблере.
Мы уже знаем, что значение (конкретное число) можно присвоить переменной, предварительно определив размер этой переменной в байт, слово, двойное слово и т.д.:
- DB — Define Bite.
- DW — Define Word.
- DD — Define Double Word.
- DQ — Define Quatro Bites.
- DT — Define Tetro Bites.
Константа — символ, синоним конкретного числа (выражения, строки), которое, в отличие от переменной нельзя изменить.
Для задания констант применяются обозначения:
- .const — все данные будут восприниматься как константы, до момента изменения сегмента (.data, .code и т.п.).
- = (знак равно).
- equ (может использоваться для создания идентификатора, константного выражения, строки).
Структура в ассемблере.
Структура в ассемблере (structure) — это совокупность переменных, объединенных одним именем. Переменные называются полями и могут быть разными по размеру. Очень удобно обращаться к данным по именам полей. Структура — основа абстракции, «блочности» кода. Понятие КЛАСС в языках высокого уровня есть не что иное, как разновидность структуры. Только в качестве полей в классе кроме данных присутствуют ещё и функции. В качестве поля в структуру может входить структура (пример — в коде).
- MY_STRUCT_1 STRUC; структура в ассемблере объявляется словом STRUC
member_1 dw ?;
member_2 db ?;
MY_STRUCT_1 ENDS;
….
- my_struct MY_STRUCT_1 <?>; структура в ассемблере, созданная на основе объявления.
- mov my_struct.member_1 ,33h;используем конкретный экземпляр в коде.
Константы, массив и структура в ассемблере — наиболее часто используемые организованные виды данных, однако есть и другие. Если посмотреть на структуру повнимательнее, со стороны нашего подхода к программированию, как к совокупности кода и данных, то остальные виды сгруппированных данных : массив, перечисление, объединение, битовые поля — фактически являются разновидностями структуры, реализованные в целях экономии процессорного времени и объёма памяти.
Массив в ассемблере.
Массив — структура данных, хранящих значения, которые идентифицируются по индексам, начиная с нулевого индекса. Рассмотрим работу с одномерным массивом — с учётом нашего начального уровня.
- my_mass_1 db 10 dup(8); создать байтовый массив, состоящий из 10 байт и заполнить его цифрами 8. Реально : дублировать (DUPlicate) 10 раз число 8.
- mov my_mass_1[0],1 ; поместить в первый байт число 1 (поля массива считаются с нуля, а не с единицы).
Перечисление в ассемблере.
Перечисления (enum) представляет собой структуру, состоящую из именованных констант. Создана для удобства программирования в Си. При этом можно просто перечислять константы, компилятор будет присваивать им целые значения в порядке возрастания, начиная с нуля. Если присвоить полю конкретное значение, которое не совпадает с простой последовательностью, отсчёт следующих, не определённых полей будет происходить по алгоритму «+1».
enum eDirection
{
RIGHT, // по умолчанию = 0
LEFT, // = 1
DOWN=5, // = 5 — присвоили, если бы не присвоить, то DOWN==2 (предыдущее поле +1)
UP // = 6 (+1)
};
В ассемблере перечисление (enum) как отдельный, самостоятельный тип данных не существует. При необходимости используют структуру с целочисленными полями либо просто отдельные целочисленные константы.
- _ENUM STRUC;В ассемблере Нет ENUM, используем STRUC
RIGHT db ?;
LEFT db ?;
DOWN db ?;
UP db ? ;
_ENUM ENDS - my_enum _ENUM <0,1,2,3>
…
- xor ax,ax;ax==0
- mov ah,my_enum.LEFT
Объединения в ассемблере.
Объединение (union) — одна и та же область памяти, используемая как разные типы данных. Естественно, в таком случае размер объединения будет равен размеру наибольшего из значений и не равна сумме длин всех запоминаемых, как в структуре. Тип данных создавался для Си, как способ экрномии памяти компьютера (сейчас — не актуально, но ранее активно использовался в написании кода, в том числе и сетевого характера, поэтому применяется и сейчас для совместимости).
- MY_UNION union
_word dw ?
_byte db ?
MY_UNION ends - _union MY_UNION <1234h>
…
После этого _union._word=1234h, а _union._byte=34h.
- xor ax,ax;ax==0
- mov ah,_union._byte
Записи с битовыми полями (запись).
Бит — единица данных, может содержать значение 1 или 0. Записи с битовыми полями (records) используют эту возможность.
Каждое битовое поле имеет заданную длину (в битах) и начальное значение. Размер данных типа записи равен сумме длин всех полей
Опять таки — удобно, экономит место и вычислительное время. Например, чтобы задать цвет точки в изображении (совокупность различных оттенков красного, зелёного, синего (RGB) или свойств окна в операционке Windows.
Не будем подробно разбирать тему, приведём пример кода.
- BitMask RECORD f0:4=1,f1:4=1,f2:4=0,f3:4=0
…
- xor ax,ax;ax==0
- mov ax,BitMask;ax==257
Работающий ассемблерный код с примерами.
Ниже приведён пример кода — GBLSTRUC.COM (вставили дополнительные строки в goblin.com). Прогоните готовый исполняемый файл через дизасемблер и дебагер при желании разобраться в вопросе поглубже.
Код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | ;GBLSTRUC.ASM .model tiny ; for СОМ .code ; code segment start org 100h ; offset in memory = 100h (for COM) start: main proc ;---------Примеры констант----------------------- .const ;--------------------- const_a db 5h const_b dd 15h const_c dw 2ah const_d db 'This is const_d string $' const_e = 33h; то же, что equ - синоним const_f equ 22h; синоним const_g equ 'This is const_g string $' .data ;---------------------- data_a db 7h data_b dd 21h data_c dw 3ah data_d db 'This is data_d string $' data_e = 55 data_f equ 33h data_g equ 'This is data_g string $' .code ;---------------------- jmp begin; DATA to begin mov ax,77h mov const_c,ax; const_с - слово, значит используем регистр длинною в слово (ax) mov data_c ,ax mov data_c,3 xor ax,ax;обнулили ax mov ah,5 mov const_a,ah; const_a - байт, значит используем часть регистра длинною в байт (ah) mov data_a ,ah; data_h dw 88h; в .code определяем константу mov data_h,99h mov ax,data_h mov data_h,ax ;---------Примеры массива----------------------- .data my_mass_1 db 10 dup(8); создать байтовый массив, состоящий из 10 байт и заполнить его цифрами 8 ;(дублировать - DUPlicate 10 раз число 8). .code mov my_mass_1[0],1 ; поместить в первый байт число 1 (поля массива считаются с нуля, а не с единицы) mov my_mass_1[7],3 ; поместить в восьмой байт число 3 mov data_h,3 ;---------Примеры структур----------------------- .data MY_STRUCT_1 STRUC;В отличие от MASM - STRUC, а не STRUCT member_1 dw ?; member_2 db ?; MY_STRUCT_1 ENDS MY_STRUCT_2 STRUC;Cтруктура в ассемблере объявляется словом STRUC member_1 dw ?; member_2 db ?; MY_STRUCT_1 <?> MY_STRUCT_2 ENDS my_struct MY_STRUCT_2 <?> .code mov my_struct.MY_STRUCT_1.member_1,5; structure mov bx, offset my_struct mov [bx].member_1,7; structure mov [bx].MY_STRUCT_1.member_2,'i' mov ax,[bx].MY_STRUCT_1.member_1 ;---------Примеры перечислений------------------- ;---Отдельного определения нет - используем структуру с полями целочисленных, именнованных констант. .data?; Не инициализированные данные, впрочем для *.com - не актуально. _ENUM STRUC;ENUM нет, используется структура в ассемблере TASM. RIGHT db ?; LEFT db ?; DOWN db ?; UP db ? ; _ENUM ENDS my_enum _ENUM <0,1,2,3> .code xor ax,ax;ax==0 mov ah,my_enum.LEFT ;---------Примеры объединений-------------------- .data MY_UNION union _word dw ? _byte db ? MY_UNION ends _union MY_UNION <1234h> ;После этого _union._word=1234h, а _union._byte=34h .code xor ax,ax;ax==0 mov ah,_union._byte ;---------Примеры записей с битовыми полями-------------------- .data BitMask RECORD f0:4=1,f1:4=1,f2:4=0,f3:4=0 .code xor ax,ax;ax==0 mov ax,BitMask;ax==257 ;------------------------------------------------ begin: mov ah,09h mov dx,offset prompt int 21h inpt: mov ah,01h int 21h cmp al,'m' je mode_man cmp al,'w' je mode_woman call my_prnt_func PASCAL,offset mes_gobl, offset mes_wow jmp begin mode_man: call my_prnt_func PASCAL, offset mes_man, offset mes_wow; addr mes_man,addr mes_man jmp cont mode_woman: call my_prnt_func PASCAL, offset mes_womn, offset mes_wow cont: ;call my_prnt_func mov ax,4c00h int 21h main endp my_prnt_func proc PASCAL pMessage1:WORD, pMessage2:WORD local tmp:WORD mov ax,pMessage2 mov tmp,ax mov ah,09h mov dx,pMessage1 int 21h mov ah,09h mov dx,tmp int 21h ret my_prnt_func endp ;DATA prompt db 'Are you Man or Woman [m/w]? : $' mes_wow db 0Dh,0Ah,"Wow!",0Dh,0Ah,24h ; строка для вывода. 24h = '$' . mes_man db 0Dh,0Ah,"Hello, Strong Man!",0Dh,0Ah,'$' ; строка для вывода. Вместо ASCII смвола '$' можно написать машинный код 24h mes_womn db 0Dh,0Ah,"Hello, Beautyful Woman!",0Dh,0Ah,'$' ; строка для вывода mes_gobl db 0Dh,0Ah,"Hello, Strong and Beautyful GOBLIN!",0Dh,0Ah,24h ; строка для вывода. 24h = '$' . len = $ - mes_gobl;len equ $ - mes_gobl end start |
Напоминаем, что данные, код и стек в программе, типа ‘*.COM’ находятся в одном и том же 16-битном сегменте. Из-за этого ошибок компилятор не выдаёт, не смотря на то, что мы меняем параметры констант. В ‘*.EXE’ файле должно быть по-другому — там код, данные, константы, инициализированные и неинициализированные переменные разделены.
На данном этапе изучения мы не будем обращать на это внимание, для нас главное — уловить суть понятий. С указанным вопросом мы столкнемся при програмировании для Windows на MASM32 и FASM, хотя практически всё можно настроить, задавая условия компиляции.