Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование на Ассемблере (masm32) под Windows. #2


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

c а й т   и з о б р е т а т е  л е й   ф о н а р и к о в   н а    с о л н е ч н ы х   б а т а р е й к а х
  Архитектура 32-битного процессора является огромной темой, которую невозможно охватить в одной или даже нескольких статьях. Поэтому мы рассмотрим ее кратко и только в той части, которая касается вопросов прикладного программирования на ассемблере под Win32: программную модель процессора, двоичную и шестнадцатеричную систему счисления, типы данных и адресацию памяти.
  Многие программируют годами, не подозревая о существовании каких-то там регистров в микропроцессоре своего компьютера. Действительно, от программиста, который пишет на языке высокого уровня, компилятор скрывает всю внутреннюю кухню, так что программист может полностью сосредоточиться на реализации алгоритма и создании пользовательского интерфейса. Однако программист, изучающий ассемблер, в первую очередь должен уяснить для себя, что такое регистры и как ими пользоваться.
  Я отношусь к регистрам как к предопределенным переменным или особому виду структур данных. Вот, к примеру, в языке Perl есть предопределенные переменные $_ и $!. Вы их сами не создаете, они просто есть. Когда говорят о программной модели процессора, имеют в виду именно набор регистров, доступных для программиста, и спользующего определенную систему команд. В программной модели имеется 31 регистр, из которых 16 являются пользовательскими и 15 системными. Нас интересуют именно пользовательские регистры, которые подразделяются на несколько групп:

  Регистры общего назначения:
EAXAXAHAL
EBXBXBHBL
ECXCXCHCL
EDXDXDHDL

EBPBP
ESPSP
ESISI
EDIDI
  Сегментные регистры:
CS
SS
DS
ES
FS
GS
  Регистры состояния и управления:
EFLAGSFLAGS
EIPIP
  Рассмотрим строение регистра на примере EAX. Регистр может принимать значения от 0 до FFFFFFFF (в шестнадцатеричном виде). Он состоит из 32 битов:

Рисунок 1.
Регистр EAX. (Увы, но его увидят только те, кто читает почту в
онлайне...

  Этот регистр делится на две равные половины - старшую и младшую - по 16 бит каждая. Старшая половина (с 16 по 31 бит) не имеет названия, и доступ к ней запрещен. Младшая половина называется "регистр AX" и занимает область с 0 по 15 бит. В свою очередь она также делится на две половины. Старшая из них - "регистр AH" (буква H означает High) - занимает область с 8 по 15 бит, младшая - "регистр AL" (буква L означает Low) - с 0 по 7 бит.
  Как я уже говорил регистры можно рассматривать как предопределенные переменные, которые можно использовать для хранения и записи каких-то значений. Допустим, поместим в EAX значение A78E5FA1, тогда AX = 5FA1, AH = 5F, AL = A1.
  До старшей половины EAX добраться напрямую нельзя, но с помощью специальной команды, о которой мы будем еще говорить, можно поменять местами значения в старшей и младшей половине регистра. Тогда EAX будет равен 5FA1A78E, AX = A78E, AH = A7, AL = 8E.
  Как правило, пользовательские регистры, кроме ESP и EBP, вы можете использовать по своему собственному усмотрению. Напишем в качестве шутки небольшую программу, предположив, что Visual Basic мог бы напрямую обращаться к регистрам процессора:
Dim strGreetings As String
strGreetings = "Hello, World!"
EDX = Len(strGreetings)
For ECX = 1 To EDX
    AL = Asc(Mid(strGreetings, ECX, 1))
    Print Chr(AL)
Next
  Понятно, что делает эта программа: она в цикле выводит на экран строку "Hello, World!" в столбик. Она довольно-таки вольно обращается с регистрами процессора, чего, конечно, нет ни в одном языке высокого уровня, но зато демонстрирует, что с регистрами и в самом деле можно работать как с обычными переменными. Однако учтите, что регистры все же не есть переменные! Они находятся не в памяти, а физически присутствуют в микропроцессоре, поэтому доступ к регистрам из программы происходит быстрее, чем к переменным.
  Вопрос: если вы создали в своей программе переменную для хранения возраста своей мамы, то будете ли вы использовать эту же переменную для хранения количества бутылок пива, выпитых на прошлой неделе? Ясно, что в здравом уме так никто не делает, так как для каждой задачи должен быть свой набор переменных. Точно так же дело обстоит и с пользовательскими регистрами процессора: хотя вы можете использовать их по собственному усмотрению, каждый регистр имеет свое назначение. Более того, существуют операции, при которых используются только определенные регистры. Рассмотрим назначение регистров общего назначения.

  EAX/AX/AH/AL - аккумулятор (Accumulator register). Регистр чаще всех остальных применяется для хранения промежуточных данных.
  EBX/BX/BH/BL - базовый регистр (Base register). Применяется для хранения базового, то есть начального, адреса объекта в памяти.
  ECX/CX/CH/CL - регистр-счетчик (Count register). Применяется в качестве счетчика в некоторых командах, которые производят повторяющиеся действия, например, в цепочечных командах.
  EDX/DX/DH/DL - регистр данных (Data register). Так же как и EAX, применяется для хранения промежуточных данных.
  EBP/BP - регистр-указатель базы кадра стека (Base Pointer register). Предназначен для удобного доступа к данным, находящимся в стеке, например, параметрам подпрограмм и локальным переменным. Избегайте использовать его для других целей.
  ESP/SP - регистр-указатель вершины стека (Stack Pointer register). Регистр неявно используется в стековых операциях. Адресует вершину стека. Избегайте применять его в операциях, не связанных со стеком. Что такое стек, мы разберем в следующих выпусках рассылки.
  ESI/SI - индекс источника (Source Index register). Применяется для загрузки адреса начала цепочки-источника при цепочечных операциях.
  EDI/DI - индекс приемника (Destination Index register). Применяется для загрузки адреса начала цепочки-приемника при цепочечных операциях.

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

  EIP/IP - указатель команд (Instruction pointer register). Регистр всегда содержит адрес следующей команды, которая будет выполнена. Программисту он непосредственно не доступен, однако чтение и запись его значения происходит в результате выполнения различных команд управления - переходов, вызова процедур, возврата из процедур.
  EFLAGS/FLAGS - регистр флагов (Flag register). Он состоит из 8 флагов состояния и 6 флагов управления.

Рис. 2.
Регистр флагов EFLAGS

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

  Таблица 1. Флаги состояния:
?битаФлагОписание
0CF - флаг переносаУстанавливается в 1, если результат математической операции не помещается в приемник или если при выполнении операции требуется заем. Также используется в командах сдвига.
2PF - флаг паритетаУстанавливается в 1, если младшие 8 бит результата операции содержат четное количество битов, равных 1.
4AF - вспомогательный флаг переносаУстанавливается в 1, если произошел заем или перенос для 3 бита результата.
6ZF - флаг нуляУстанавливается в 1, если результат операции равен 0, иначе сбрасывается в 0.
7SF - флаг знакаПрименяется при работе с числами со знаком, так как дублирует состояние старшего бита результата, который определяет его знак: 0 - результат является положительным числом, 1 - отрицательным.
11OF - флаг переполненияПрименяется при работе с числами со знаком, так как устанавливается в 1, eсли результат операции выйдет за пределы допустимого значения для результата.
12-13IOPL - уровень привилегий ввода-выводаИспользуется в защищенном режиме. Показывает минимальный уровень привилегий выполняющейся задачи, на котором разрешается совершение операций ввода-вывода.
14NT - флаг вложенности задачиИспользуется в защищенном режиме. Указывает, что одна задача вложена в другую.
  Таблица 2. Флаги управления
? битаФлагОписание
8TF - флаг трассировкиЕсли флаг установлен, то процессор переходит в режим покомандной работы. Используется при отладке программ отладчиками.
9IF - флаг прерыванияЕсли флаг установлен, то аппаратные прерывания разрешены.
10DF - флаг направленияЗадает направление обработки цепочек. Если флаг установлен, то значение регистров ESI и EDI при цепочечных операциях будет автоматически уменьшаться, то есть цепочки будут обрабатываться от конца к началу.
16RF - флаг возобновления Используется совместно с регистрами отладки.
17VM - флаг виртуального режима процессора 8086Если флаг установлен, то процессор работает в режиме виртуального 8086.
18AC - флаг контроля выравниванияЕсли флаг установлен, то разрешен контроль выравнивания при обращении к памяти.
  Чтобы вам стало более понятно, как используются флаги, напишем простую программу на Visual Basic, опять наделив его несуществующими возможностями прямой манипуляции регистрами и флагами:
ECX = EAX - 5
If ZF = 1 Then
 Print "ECX = 0"
Else
 Print "ECX <> 0"
End If
  В нашей программе в регистр ECX записывается результат вычитания числа 5 из значения, хранящегося в EAX. Если ECX будет равен 0, то процессор установит флаг ZF в 1, иначе он сбросит флаг в 0. В программе проверяется состояние флага ZF, и на его основе принимается решение, какое сообщение выводить на экран.
  Если вы ничего не поняли из описания регистров и флагов, не огорчайтесь. Подробнее мы будем изучать работу с ними в контексте конкретных операций.
  Считается, что десятичная система счисления возникла и распространилась повсеместно потому, что у человека десять пальцев на руках. Эх, почему у человека на руках не шестнадцать пальцев? Однако рано огорчаться, с нынешней экологией еще не все потеряно :)
  Компьютер, в отличие от человека, считает в двоичной системе счисления. Ученые и инженеры, работавшие над архитектурой первых компьютеров, пришли к выводу, что для создания простого и мощного процессора достаточно только научить его распознавать возбужденное и невозбужденное состояние электрических элементов. Программисты ради удобства возбужденное состояние рассматривают как 1, невозбужденное как 0.
  Десятичная система является поместной или, как еще говорят, позиционной. То есть одна и та же цифра может обозначать разные числа, в зависимости от занимаемого ею места - позиции. К примеру, в числе 31 цифра 3 означает 30, так как она занимает место, предназначенное для десятков, а в числе 301 она означает 300. То же самое справедливо для двоичных чисел, с той лишь разницей, что позиции цифр показывают степени числа 2.

Рис. 3.
Таблица квадратов двойки


  Возьмем, к примеру, двоичное число 10011010. Найдем по таблице (рис. 5) степени двойки для каждой единицы и представим его значение в десятичной системе:
1*(2^7)+0*(2^6)+0*(2^5)+1*(2^4)+1*(2^3)+0*(2^2)+1*(2^1)+0*(2^0) =
1*128+0*64+0*32+1*16+1*8+0*4+1*2+0*1 = 154
  Для преобразования числа из десятичной системы в двоичную, необходимо десятичное число последовательно делить на 2, запоминая остатки от деления. Когда результат деления будет равен 0, то остатки нужно переписать в обратном порядке. Полученное число будет двоичным представлением исходного десятичного числа. Например, преобразуем число 275:
275 / 2 = 137 (1)
137 / 2 = 68  (1)
68  / 2 = 34  (0)
34  / 2 = 17  (0)
17  / 2 = 8   (1)
8   / 2 = 4   (0)
4   / 2 = 2   (0)
2   / 2 = 1   (1)
1   / 2 = 0   (1)
----------------
100010011
  Однако удобнее совершать преобразования с использованием таблицы квадратов двойки. Преобразуем число 453. По таблице (рис. 5) ближайшим меньшим числом к 453 является 256. Следовательно, в числе 453 всего 8 позиций, начиная с нулевой. Из той же таблицы видно, что 453 = 256 + 128+64 + 4 + 1. Значит, на 8, 7, 6, 2 и 0 позициях расположены единицы. Получается 111000101.
  Для того чтобы считать в двоичной системе, необходимо вспомнить правило, которым мы руководствуемся при счете в десятичной системе: если значение, получаемое в разряде, превосходит основание системы счисления, то выполняется перенос 1 в левый разряд. Сложим два двоичных числа:

Рис. 4. Сложение двух двоичных
чисел

  1) В нулевом разряде у нас 0 и 1. Сложив их, получаем 1.
  2) Во втором разряде у нас 1 и 1. Сложив их, получаем 2, но так как двойку мы записать не можем, результат сбрасывается в 0, а в переносы записываем 1.
  3) В третьем разряде у нас 1 и 0. Сложив их, получаем 1, но кроме того, у нас имеется 1 в переносе, которую мы тоже должны прибавить. Получается 1+0+1 = 2. В итоге результат сбрасывается в 0, а в переносы опять записывается 1.
  4) В четвертом разряде у нас 0 и 1, а также 1 в переносе. 0+1+1 = 2. Записываем в результат 0 и в перенос единицу. И т.д.
  Как видите, двоичная система счисления проста, но человеку довольно трудно интерпретировать двоичную информацию. Поэтому для удобства была создана шестнадцатеричная система счисления, в основе которой лежит понятие тетрады - последовательности из 4 нулей и единиц. Существует 16 возможных тетрад:

  Таблица 3. Двоичные тетрады и соответствующие им десятичные и шестнадцатеричные числа.

Десятичное числоДвоичная тетрадаШестнадцатеричное число
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A или a
111011B или b
121100C или c
131101D или d
141110E или e
151111F или f
  Теперь двоичное число 10011010 можно представить в шестнадцатеричном виде как 9A, разбив его на две тетрады 1001 и 1010 и найдя значение каждой тетрады по таблице. Для того чтобы найти его значение в десятичном виде, надо воспользоваться таблицей квадратов числа 16:

рис. 5.
Таблица квадратов числа 16

  Результат будет 154, то есть 9*16+10*1.

  Для преобразования десятичного числа в шестнадцатеричное, необходимо сначала преобразовать его в двоичное, а затем в шестнадцатеричное, разбив на тетрады.
  Считать в шестнадцатеричной системе так же просто, как и в двоичной:

Рис. 6.
Сложение двух шестнадцатеричных чисел

  Возьмем двоичное число 10110010 01101110. Каждый 0 или 1 в нем называется битом, который является минимальной единицей информации. Последовательность из 8 бит называется байтом. Таким образом, это число состоит из 2 байтов. Именно байт, а не бит является типом данных, который аппаратно поддерживается процессором. Нумеровать биты в байте принято справа налево, начиная с нулевого бита, как мы это делали в предыдущих примерах. Бит 0 называется младшим битом, а бит 7 называется старшим битом. Кроме байтов, процессор аппаратно поддерживает и другие типы данных:

  Слово - последовательность из двух байт или 16 битов. Биты в слове так же, как и в байте, нумеруются справа налево от 0 до 15. Байт, который занимает диапазон с 0 по 7 бит, называется в слове младшим байтом, а байт, который занимает диапазон с 8 по 15 бит - старшим байтом.
  Двойное слово - последовательность из двух слов. Биты в двойном слове нумеруются справа налево от 0 до 31. Слово, занимающее диапазон с 0 по 15 бит, называется младшим словом, а занимающее диапазон с 16 по 31 бит называется старшим словом.
  Учетверенное слово - последовательность из двух двойных слов. Биты в учетверенном слове нумеруются справа налево от 0 до 63. Двойное слово, занимающее диапазон с 0 по 31 бит, называется младшим двойным словом, двойное слово, занимающее диапазон с 32 по 63 бит, называется старшим двойным словом.

  Какие максимальные и минимальные значения могут содержать данных этих типов? Это зависит от того, как интерпретировать данные: как целые числа со знаком или без знака. Возьмем двоичное число 11111111. Его размер - байт, так как оно 8-разрядное. Его значение легко найти с помощью описанных выше правил: 255. Другим конечным значением для байта является двоичное число 00000000 или в десятичной системе все тот же ноль. Означает ли это, что в байте могут храниться только положительные числа? Да, если только не применить одну хитрость: старший бит можно рассматривать как признак знака. Если он равен 1, то число можно считать отрицательным, которое записано в дополнительном коде. Остальные биты представляют беззнаковое абсолютное значение. Тогда в байте можно хранить значение со знаком, и для него максимальным значением будет двоичное число 01111111 (в десятичной системе +127), а минимальным значением 10000000 (в десятичной системе -128). Эти же правила справедливы и для других типов данных.

  Таблица 4. Размерность типов данных
Тип данныхСо знакомБез знака
БайтОт -128 до +127От 0 до 255
СловоОт -32768 до +32767От 0 до 65535
Двойное словоОт -2147483648 до +2147483647От 0 до 4294967295
  Учтите, что 0 в программировании считается положительным числом.

  Для преобразования числа в дополнительный код необходимо инвертировать все биты числа, то есть 1 превратить в 0, а 0 в 1, и прибавить к результату 1.
  Например, число -10 в виде байта компьютере представляется как двоичное число 11110110. Повторим все действия процессора, чтобы получить тот же результат. Число 10 в виде байта в двоичном виде равно 00001010. Инвертировав все биты, получаем 11110101. Теперь прибавляем 1 и получаем 11110110.
  Если вы откроете SoftICE, то увидите, что размер сегментных регистров - слово, а всех остальных регистров - двойное слово. Естественно, что двойное слово является для 32-битных процессоров и 32-битных операционных систем основным типом данных: и пользовательские регистры, и адреса памяти имеют размер двойного слова.
  Регистры мы уже разбирали, поговорим теперь о памяти. Память в компьютере является массивом байтов, каждый их которых имеет свой адрес, то есть число, которое определяет его местоположение в памяти. Точно так же каждая фигура на шахматной доске имеет свой адрес: e4, g8, a1 и т.д. Только память компьютера скорее похожа на ленту, чем на шахматную доску.

Рис. 7.
Адреса памяти

  Рассмотрим, как располагаются в памяти, различные типы данных. На рисунке 7 изображен сплошной массив байтов, находящихся в памяти. В верхней части находятся старшие адреса, а в нижней - младшие. Допустим, нас интересует байт, находящийся по адресу A0404003. Находим в нужной ячейке его значение: 5С. Что скажете насчет слова по этому же адресу? Здесь вступает в действие важное правило: адресом любой структуры данных или типа является адрес его младшего байта, а младший байт хранится по младшему адресу. Значит, слово, расположенное по адресу A0404003 имеет значение FF5С. Соответственно, двойное слово по этому адресу будет иметь значение B28BFF5C. Не желаете ли проверить? Напишем для этого программу:
1  .386
2  .model flat, stdcall
3  option casemap: none

4  .data
5  dwNum dword 0

6  .code
7  start:
8   mov dwNum, 0B28BFF5Ch
9   ret
10  end start
  Сохраните ее в файле \masm32\win32asm\2\dwmem.asm и откомпилируйте с отладочной информацией так, как мы это делали в предыдущей статье. Если вы следовали указаниям предыдущей статьи, то вы должны были создать файл db.bat для загрузки отладочной информации в SoftICE с помощью утилиты NMSYM.EXE. Этот файл вы должны были сохранить в директории \masm32\bin и прописать путь к ней в переменной окружения PATH. Теперь если мы в командной строке дадим команду db dwmem.exe (находясь в FAR'e, это легко сделать так: написать db, а затем встать на файл dwmem.exe и нажать CTRL+ENTER), NMSYM.EXE загрузит программу в SoftICE и тот прервет ее исполнение на точке входа. Когда отладчик всплывет, вы увидете, что весь экран заполнен словами INVALID. Это так и должно быть. Нажмите один раз F8 и вы увидете исходный код своей программы. Если окно дампа памяти у вас скрыто, дайте команду wd 4. Послушайте моего совета и пропишите эту команду в строке инициализации отладчика. Как это сделать, сказано в нулевом (вводном) номере рассылки. Затем дайте команду d dwNum и отладчик покажет в окне дампа памяти, что находится в памяти, начиная с адреса 00404004h. В заголовке окна SoftICE так и напишет: "dwNum". 00404004h - это адрес переменной dwNum. Вспомним, что переменная есть именованная область памяти. Нажмите еще раз клавишу F8 (можно дать команду t - от слова trace), чтобы выполнить инструкцию "mov dwNum, 0B28BFF5Ch". С командой mov я вас еще не знакомил. С ней разберемся позже. Сейчас скажу лишь, что в ассемблере с помощью этой команды совершается пересылка данных. В языках высокого уровня для этого служат операторы присваивания: в Паскале ":=", в С и Бейсике "=". Вгляните теперь на окно дампа памяти. По адресу 00404004h в четырех байтах записаны значения: 5C FF 8B B2. Теперь вспомним правило, ради проверки которого мы затеяли все эти манипуляции: в процессорах фирмы Intel адресом любой структуры данных или типа является адрес его младшего байта, а младший байт хранится по младшему адресу. Следовательно, если мы хотим выяснить, чему равно двойное слово, хранящееся по адресу 00404004h, нам надо читать эти 4 байта задом наперед: B2 8B FF 5C. Оно?
  К такому "перевернутому" представлению данных достаточно сложно привыкнуть, но ничего не поделаешь - тем самым достигается более быстрый доступ процессора к ячейкам памяти. Кстати, SoftICE может показывать дамп памяти не только в виде последовательности байтов, но и слов, двойных слов, коротких, длинных и 10-байтовых вещественных чисел. Для этого служат команды db, dw, dd, ds, dl, dt. Дадим ему команду dd. О чудо! Значение нашей переменной встало с головы на ноги! Выйдете из отладчика одним из трех способов: X, CTRL+D, F5.

  Двигаемся дальше?
  Как вам известно, операционные системы семейства Win32 являются многозадачными, то есть на одном компьютере под управлением Win32 можно запустить сразу несколько программ. При этом код и данные этих программ не смешиваются, так как каждая из них выполняется в своем собственном виртуальном адресном пространстве. И каждое из этих пространств имеет размер 4 Gb. То есть, как я уже говорил, память для 32-разрядного приложения в Win32 представляется в виде сплошного массива байтов от 0 до FFFFFFFF. Такая модель памяти называется плоской. Взгляните на вторую строку примера fapp.asm. Директива .model flat определяет плоскую модель памяти, характерную для Windows-приложений. Собственно, и директива .386 в первой строке вставлена именно для того, чтобы можно было определить плоскую модель памяти, так как, начиная с процессора 80386, процессоры фирмы Intel стали 32-разрядными, что позволило создавать операционные системы, которые давали плоскую память. Для того чтобы вам стали понятнее преимущества плоской памяти, кратко рассмотрим память 16-разрядной MS-DOS. Какое пространство памяти может адресовать 16-битный адрес? 65535 байт, или 64 Kb. Для того чтобы преодолеть это рубеж, была придумана уловка, которая состояла в разбиении памяти на блоки по 64 Kb, называемых сегментами. Память могла быть разделена максимум на 65535 сегментов. Внутри одного сегмента программист мог адресовать 65535 байт. То есть адрес памяти существовал в виде адреса сегмента в оперативной памяти плюс смещение внутри сегмента. Он записывался, например, так: B05A:FC61.
  Программисту нужно было всегда думать, где находятся нужные данные - в текущем сегменте или в каком-то другом. Адреса внутри текущего сегмента назывались ближними, а адреса внутри любого другого сегмента назывались дальними. Переход по дальнему адресу совершался в два приема: сначала загрузка адреса нужного сегмента, потом сам переход. В плоской памяти все адреса являются ближними.
  Однако было бы лучше, если бы вы забыли все, что я сейчас рассказал о сегментации памяти. Это дело давнее и нас не касается. Итак, windows-программа на сегменты не разделяется. Но она делится на секции: секция инициализированных данных (.data), секция неинициализированных данных (.data?), секция кода (.code), секция констант (.const). В первую очередь нас интересуют секции данных. В них размещаются в основном объявления глобальных переменных. Я иной раз не туда ткну, и в секции данных у меня оказываются макросы, прототипы функций, и ничего, программа компилируется. Однако это сейчас не главное. Поговорим все же о переменных.
  Переменные, которым при объявлении присваиваются какие-то значения, объявляются в секции .data, а неинициализированные переменные объявляются в секции .data? Для определения типов данных переменных в MASM используются следующие директивы:

  Таблица 5. Директивы целочисленных типов данных
ДирективаТип данных и размер в байтахОписание
byte, dbБайт (1)Выделяет память для хранения целых чисел без знака от 0 до 255
sbyteБайт (1)Выделяет память для хранения целых чисел со знаком от -128 до +127
word,dwСлово (2)Выделяет память для xранения целых чисел без знака от 0 до 65535
swordСлово (2)Выделяет память для хранения целых чисел со знаком от -32767 до +32768
dword, ddДвойное слово (4)Выделяет память для хранения целых чисел без знака от 0 до 4294967295
sdwordДвойное слово (4)Выделяет память для хранения целых чисел со знаком от -2147483648 до +2147483647
fword, dfДальнее слово (6)Выделяет память для хранения 48-битного целого числа, использующегося в качестве указателя на память, состоящего из 16-разрядной сегментной части (селектора) и 32-разрядного смещения
qword, dqУчетверенное слово (8)Выделяет память для хранения 64-разрядного целого числа
tbyte, dtТерабайт (10)Выделяет память для хранения 80-разрядного целого числа
  Кроме того, в MASM существуют директивы для объявления переменных, хранящих числа с плавающей точкой:

  Таблица 6. Директивы вещественных типов данных
ДирективаТип данных и размер в байтахОписание
Real4Короткое вещественное число (4)Выделяет память для хранения вещественных чисел со знаком от 1.18х10^38 до 3.40x10^38
Real8Длинное вещественное число (8)Выделяет память для хранения вещественных чисел со знаком от 2.23х10^308 до 1.79x10^308
Real1010-байтовое вещественное число и BCD-числоВыделяет память для хранения вещественных чисел со знаком от 3.37х10^4932 до 1.18x10^4932
  С помощью этих директив переменные объявляются таким образом:

Рис.
8. Объявление переменных в программе

  Объявление инициализированных переменных предваряется директивой .data, а затем указываются имена переменных, их типы и начальные значения. Кроме того, на рисунке 10 продемонстрированы формы записи чисел в разных системах счисления. К числу в двоичной записи в конце обязательно добавляется буква B или b - binary. К числу в шестнадцатеричной записи в конце добавляется буква H или h - hexadecimal. Если hex-число начинается с буквы, то в начале к нему дописывается 0, как это сделано при объявлении переменной mem16. К числам в десятичной записи можно в конце добавлять букву D или d - decimal, но можно и не добавлять, так как десятичная форма записи чисел для MASM является формой по умолчанию. Форму по умолчанию можно сменить с помощью директивы .radix, однако я вам не советую это делать, так как в файле windows.inc и других файлах из MASM32 константы определены как в десятичном, так и в шестнадцатеричном виде.
  Неинициализированные переменные объявляются точно также, только в секции .data? Надо сказать, что инициализированные данные хранятся прямо в исполняемом файле, в то время как неинициализированные динамически создаются в памяти. Поэтому большие массивы лучше объявлять неинициализированными. Это позволит заметно сократить размер исполняемого файла.
  Константы объявляются в секции .const подобно обычным переменным. Однако, естественно, их значение не может переопределяться в ходе выполнения программы.
  В следующем номере:
  - машинные коды и ассемблер;
  - команды MOV и XCHG;
  - использование указателей в операциях пересылки данных;
  - о других командах пересылки данных.
Copyright 2001 HI-TECH. По всем вопросам мылить hi-tech@tut.by

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу
Рейтингуется SpyLog

В избранное