Показаны сообщения с ярлыком binary-coded decimal. Показать все сообщения
Показаны сообщения с ярлыком binary-coded decimal. Показать все сообщения

вторник, 2 июня 2009 г.

И снова о данных

На просторах Интернета, нашел дополнительную информацию по определению и использованию данных в языке ассемблера. Хоть тут будут и небольшие повторения, но повторение - мать учения.

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

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

  3. Данные сложного типа, которые были введены в язык ассемблера с целью облегчения разработки программ. Сложные типы данных строятся на основе базовых типов, которые являются как бы кирпичиками для их построения. Введение сложных типов данных позволяет несколько сгладить различия между языками высокого уровня и ассемблером. У программиста появляется возможность сочетания преимуществ языка ассемблера и языков высокого уровня (в направлении абстракции данных), что в конечном итоге повышает эффективность конечной программы.
Понятие простого типа данных носит двойственный характер. С точки зрения размерности (физическая интерпретация), микропроцессор аппаратно поддерживает следующие основные типы данных (рис.1):
  • байт — восемь последовательно расположенных битов, пронумерованных от 0 до 7, при этом бит 0 является самым младшим значащим битом;
  • слово — последовательность из двух байт, имеющих последовательные адреса. Размер слова — 16 бит; биты в слове нумеруются от 0 до 15. Байт, содержащий нулевой бит, называется младшим байтом, а байт, содержащий 15-й бит - старшим байтом. Микропроцессоры Intel имеют важную особенность — младший байт всегда хранится по меньшему адресу. Адресом слова считается адрес его младшего байта. Адрес старшего байта может быть использован для доступа к старшей половине слова.
  • двойное слово — последовательность из четырех байт (32 бита), расположенных по последовательным адресам. Нумерация этих бит производится от 0 до 31. Слово, содержащее нулевой бит, называется младшим словом, а слово, содержащее 31-й бит, - старшим словом. Младшее слово хранится по меньшему адресу. Адресом двойного слова считается адрес его младшего слова. Адрес старшего слова может быть использован для доступа к старшей половине двойного слова.
  • учетверенное слово — последовательность из восьми байт (64 бита), расположенных по последовательным адресам. Нумерация бит производится от 0 до 63. Двойное слово, содержащее нулевой бит, называется младшим двойным словом, а двойное слово, содержащее 63-й бит, — старшим двойным словом. Младшее двойное слово хранится по меньшему адресу. Адресом учетверенного слова считается адрес его младшего двойного слова. Адрес старшего двойного слова может быть использован для доступа к старшей половине учетверенного слова.
Кроме трактовки типов данных с точки зрения их разрядности, микропроцессор на уровне команд поддерживает логическую интерпретацию этих типов (рис.2):
  • Целый тип со знаком — двоичное значение со знаком, размером 8, 16 или 32 бита. Знак в этом двоичном числе содержится в 7, 15 или 31-м бите соответственно. Ноль в этих битах в операндах соответствует положительному числу, а единица — отрицательному. Отрицательные числа представляются в дополнительном коде. Числовые диапазоны для этого типа данных следующие:
    • 8-разрядное целое — от –128 до +127;
    • 16-разрядное целое — от –32 768 до +32 767;
    • 32-разрядное целое — от –231 до +231–1.
  • Целый тип без знака — двоичное значение без знака, размером 8, 16 или 32 бита. Числовой диапазон для этого типа следующий:
    • байт — от 0 до 255;
    • слово — от 0 до 65 535;
    • двойное слово — от 0 до 232–1.
  • Указатель на память двух типов:
    • ближнего типа — 32-разрядный логический адрес, представляющий собой относительное смещение в байтах от начала сегмента. Эти указатели могут также использоваться в сплошной (плоской) модели памяти, где сегментные составляющие одинаковы;
    • дальнего типа — 48-разрядный логический адрес, состоящий из двух частей: 16-разрядной сегментной части — селектора, и 32-разрядного смещения.
  • Цепочка — представляющая собой некоторый непрерывный набор байтов, слов или двойных слов максимальной длины до 4 Гбайт.
  • Битовое поле представляет собой непрерывную последовательность бит, в которой каждый бит является независимым и может рассматриваться как отдельная переменная. Битовое поле может начинаться с любого бита любого байта и содержать до 32 бит.
  • Неупакованный двоично-десятичный тип — байтовое представление десятичной цифры от 0 до 9. Неупакованные десятичные числа хранятся как байтовые значения без знака по одной цифре в каждом байте. Значение цифры определяется младшим полубайтом.
  • Упакованный двоично-десятичный тип представляет собой упакованное представление двух десятичных цифр от 0 до 9 в одном байте. Каждая цифра хранится в своем полубайте. Цифра в старшем полубайте (биты 4–7) является старшей.
Отметим, что “Зн” на рис. 2 означает знаковый бит.

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

На рис. 3 использованы следующие обозначения:
  • ? показывает, что содержимое поля не определено, то есть при задании директивы с таким значением выражения содержимое выделенного участка физической памяти изменяться не будет. Фактически, создается неинициализированная переменная;
  • значение инициализации — значение элемента данных, которое будет занесено в память после загрузки программы. Фактически, создается инициализированная переменная, в качестве которой могут выступать константы, строки символов, константные и адресные выражения в зависимости от типа данных. Подробная информация приведена в приложении 1;
  • выражение — Эта конструкция позволяет повторить последовательное занесение в физическую память выражения в скобках n раз.
  • имя — некоторое символическое имя метки или ячейки памяти в сегменте данных, используемое в программе.
Директивы описания и инициализации данных:
  • db — резервирование памяти для данных размером 1 байт.
    Директивой db можно задавать следующие значения:
    • выражение или константу, принимающую значение из диапазона:
      • для чисел со знаком –128...+127;
      • для чисел без знака 0...255;
    • 8-битовое относительное выражение, использующее операции HIGH и LOW;
    • символьную строку из одного или более символов. Строка заключается в кавычки. В этом случае определяется столько байт, сколько символов в строке.
  • dw — резервирование памяти для данных размером 2 байта.
    Директивой dw можно задавать следующие значения:
    • выражение или константу, принимающую значение из диапазона:
      • для чисел со знаком –32 768...32 767;
      • для чисел без знака 0...65 535;
    • выражение, занимающее 16 или менее бит, в качестве которого может выступать смещение в 16-битовом сегменте или адрес сегмента;
    • 1- или 2-байтовую строку, заключенная в кавычки.
  • dd — резервирование памяти для данных размером 4 байта.
    Директивой dd можно задавать следующие значения:
    • выражение или константу, принимающую значение из диапазона:
      • для i8086:
        • для чисел со знаком –32 768...+32 767;
        • для чисел без знака 0...65 535;
      • для i386 и выше:
        • для чисел со знаком –2 147 483 648...+2 147 483 647;
        • для чисел без знака 0...4 294 967 295;
    • относительное или адресное выражение, состоящее из 16-битового адреса сегмента и 16-битового смещения;
    • строку длиной до 4 символов, заключенную в кавычки.
  • df — резервирование памяти для данных размером 6 байт;
  • dp — резервирование памяти для данных размером 6 байт.
    Директивами df и dp можно задавать следующие значения:
    • выражение или константу, принимающую значение из диапазона:
      • для i8086:
        • для чисел со знаком –32 768...+32 767;
        • для чисел без знака 0...65 535;
      • для i386 и выше:
        • для чисел со знаком –2 147 483 648...+2 147 483 647;
        • для чисел без знака 0...4 294 967 295;
    • относительное или адресное выражение, состоящее из 32 или менее бит (для i80386) или 16 или менее бит (для младших моделей микропроцессоров Intel);
    • адресное выражение, состоящее из 16-битового сегмента и 32-битового смещения;
    • константу со знаком из диапазона –247...247–1;
    • константу без знака из диапазона 0...248-1;
    • строку длиной до 6 байт, заключенную в кавычки.
  • dq — резервирование памяти для данных размером 8 байт.
    Директивой dq можно задавать следующие значения:
    • выражение или константу, принимающую значение из диапазона:
      • для МП i8086:
        • для чисел со знаком –32 768...+32 767;
        • для чисел без знака 0...65 535;
      • для МП i386 и выше:
        • для чисел со знаком –2 147 483 648...+2 147 483 647;
        • для чисел без знака 0...4 294 967 295;
    • относительное или адресное выражение, состоящее из 32 или менее бит (для i80386) или 16 или менее бит (для младших моделей микропроцессоров Intel);
    • константу со знаком из диапазона –263...263–1;
    • константу без знака из диапазона 0...264–1;
    • строку длиной до 8 байт, заключенную в кавычки.
  • dt — резервирование памяти для данных размером 10 байт.
    Директивой dt можно задавать следующие значения:
    • выражение или константу, принимающую значение из диапазона:
      • для МП i8086:
        • для чисел со знаком –32 768...+32 767;
        • для чисел без знака 0...65 535;
      • для МП i386 и выше:
        • для чисел со знаком –2 147 483 648...+2 147 483 647;
        • для чисел без знака 0...4 294 967 295;
    • относительное или адресное выражение, состоящее из 32 или менее бит (для i80386) или 16 или менее бит (для младших моделей);
    • адресное выражение, состоящее из 16-битового сегмента и 32-битового смещения;
    • константу со знаком из диапазона –279...279-1;
    • константу без знака из диапазона 0...280-1;
    • строку длиной до 10 байт, заключенную в кавычки;
    • упакованную десятичную константу в диапазоне 0...99 999 999 999 999 999 999.

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

А теперь примерчик для лучшего усвоения прочитанного:

А так же его листинг:

Ну и теперь медитация в дебагере...

Внимательно медитируем и прозреваем...
Еще раз видим, что данные располагаются в памяти в "обратной" последовательности, то есть принцип - младший байт, по младшему адресу.
В дампе памяти видим данные вашего сегмента в двух представлениях: шестнадцатеричном и символьном. Видно, что со смещением 0000 расположены символы, входящие в строку message. Она занимает 34 байта. После нее следует байт, имеющий в сегменте данных символическое имя perem_1, содержимое этого байта offh.
Теперь обратите внимание на то, как размещены в памяти байты, входящие в слово, обозначенное символическим именем perem_2. Сначала следует байт со значением 7fh, а затем со значением 3ah. Как видите, в памяти действительно сначала расположен младший байт значения, а затем старший. Та же история и с данными обозначенными символическим именем perem_3.
Оставшуюся часть сегмента данных вы можете теперь проанализировать самостоятельно.
Остановимся лишь на двух специфических особенностях использования директив резервирования и инициализации памяти. Речь идет о случае использования в поле операндов директив dw и dd символического имени из поля имя этой или другой директивы резервирования и инициализации памяти. В нашем примере сегмента данных это директивы с именами adr и adr_full.
Когда транслятор встречает директивы описания памяти с подобными операндами, то он формирует в памяти значения адресов тех переменных, чьи имена были указаны в качестве операндов. В зависимости от директивы, применяемой для получения такого адреса, формируется либо полный адрес (директива dd) в виде двух байтов сегментного адреса и двух байтов смещения, либо только смещение (директива dw).

Любой переменной, объявленной с помощью директив описания простых типов данных, ассемблер присваивает три атрибута:

  1. Сегмент (seg) — адрес начала сегмента, содержащего переменную;
  2. Смещение (offset) в байтах от начала сегмента с переменной;
  3. Тип (type) — определяет количество памяти, выделяемой переменной в соответствии с директивой объявления переменной.

суббота, 30 мая 2009 г.

Представление данных

Теперь, опять, немного матчасти :) Надо бы поподробней разобраться с представлением данных...

В языке ассемблера имеются средства записи целых и вещественных чисел, а также символьных строк и отдельных символов. Целые числа могут быть со знаком и без знака, а также записанными в двоично-десятичном формате. Для целых чисел и символов в составе команд микропроцессора и, соответственно, в языке ассемблера, есть средства обработки - анализа, сравнения, поиска и проч. Для вещественных чисел таких средств в самом микропроцессоре нет, они содержатся в арифметическом сопроцессоре.
Рассмотрим сначала целые числа без знака и со знаком. Числа без знака получили свое название потому, что среди этих чисел нет отрицательных. Это самый простой вид чисел: они представляют собой весь диапазон двоичных чисел, которые можно записать в байте, слове или двойном слове. Для байта числа без знака могут принимать значения от 00h (0) до FFh (255); для слова - от 0000h (0) до FFFFh (65535); для двойного слова - от 00000000h (0) до FFFFFFFFh (4294967295).
В огромном количестве приложений вычислительной техники для чисел нет понятия знака. Это справедливо, например, для адресов ячеек памяти, кодов ASCII символов, результатов измерений многих физических величин, кодов управления устройствами, подключаемыми к компьютеру. Для таких чисел естественно использовать весь диапазон чисел, записываемых в ячейку того или иного размера. Если, однако, мы хотим работать как с положительными, так и с отрицательными числами, нам придется половину чисел из их полного диапазона считать положительными, а другую половину - отрицательными. В результате диапазон изменения числа уменьшается в два раза. Кроме того, необходимо предусмотреть систему кодирования, чтобы положительные и отрицательные числа не перекрывались.
В вычислительной технике принято записывать отрицательные числа в так называемом дополнительном коде, который образуется из прямого путем замены всех двоичных нулей единицами и наоборот (обратный код) и прибавления к полученному числу единицы. Это справедливо как для байтовых (8-битовых) чисел, так и для чисел размером в слово или в двойное слово (рис. 1)

Рис. 1. Образование отрицательных чисел различного размера.
Такой способ образования отрицательных чисел удобен тем, что позволяет выполнять над ними арифметические операции по общим правилам с получением правильного результата. Так, сложение чисел +5 и -5 дает 0; в результате вычитания 3 из 5 получается 2; вычитание -3 из -5 дает -2 и т.д.
Анализируя алгоритм образования отрицательного числа, можно заметить, что для всех отрицательных чисел характерно наличие двоичной единицы в старшем бите. Положительные числа, наоборот, имеют в старшем бите 0. Это справедливо для чисел любого размера. Кроме того, из рис. 1 видно, что для преобразования отрицательного 8-битового числа в слово достаточно дополнить его слева восемью двоичными единицами. Легко сообразить, что для преобразования положительного 8-битового числа в слово его надо дополнить восемью двоичными нулями. То же справедливо и для преобразования слова со знаком в двойное слово со знаком, только добавить придется уже не 8, а 16 единиц или нулей. В системе команд МП 86 и, соответственно, в языке ассемблера, для этих операций предусмотрены специальные команды cbw и cwd.
Следует подчеркнуть, что знак числа условен. Одно и то же число, например, изображенное на рис. 1 8-битовое число FBh можно в одном контексте рассматривать, как отрицательное (-5), а в другом - как положительное, или, правильнее, число без знака (FBh=251). Знак числа является характеристикой не самого числа, а нашего представления о его смысле.
На рис. 2 представлена выборочная таблица 16-битовых чисел с указанием их машинного представления, а также значений без знака и со знаком. Из таблицы видно, что для чисел со знаком размером в слово диапазон положительных значений простирается от 0 до 32767, а диапазон отрицательных значений - от -1 до -32768.

Рис. 2 Представление 16-битовых чисел без знака и со знаком
На рис. 3 представлена аналогичная таблица для 8-битовых чисел. Из таблицы видно, что для чисел со знаком размером в байт диапазон положительных значений простирается от 0 до 127, а диапазон отрицательных значений - от -1 до -128.

Рис. 3 Представление 8-битовых чисел без знака и со знаком
Среди команд процессора, выполняющих ту или иную обработку чисел, можно выделить команды, безразличные к знаку числа (например, inc, add, test), команды, предназначенные для обработки чисел без знака (mul, div, ja, jb и др.), а также команды, специально рассчитанные на обработку чисел со знаком (imul, idiv, jg, jl и т.д.).
Рассмотрим теперь другой вид представления чисел - двоично-десятичный формат (binary-coded decimal , BCD), используемый в ряде прикладных областей. В таком формате выдают данные некоторые измерительные приборы; он же используется КМОП-часами реального времени компьютеров IBM PC для хранения информации о текущем времени. В МП 86 предусмотрен ряд команд для обработки таких чисел.
Двоично-десятичный формат существует в двух разновидностях: упакованный и распакованный. В первом случае в байте записывается двухразрядное десятичное число от 00 до 99. Каждая цифра числа занимает половину байта и хранится в двоичной форме. Из рис. 4 можно заметить, что для записи в байт десятичного числа в двоично-десятичном формате достаточно сопроводить записываемое десятичное число символом h.

Рис. 4 Упакованный двоично-десятичный формат
В машинном слове или в 16-разрядном регистре можно хранить в двоично-десятичном формате четырехразрядные десятичные числа от 0000 до 9999 (рис.5).

Рис. 5 Запись десятичного числа 9604 в слове
Распакованный формат отличается от упакованного тем, что в каждом байте записывается лишь одна десятичная цифра (по-прежнему в двоичной форме). В этом случае в слове можно записать десятичные числа от 00 до 99 (см. рис. 6)

Рис. 6 Запись десятичного числа 98 в распакованном виде
При хранении десятичных чисел в аппаратуре обычно используется более экономный упакованный формат; умножение и деление выполняются только с распакованными числами, операции же сложения и вычитания применимы и к тем, и к другим.