Воробьёвы

(-:

№19. Печать шестнадцатеричных циферек :)

КОЛОНКА ВРЕДАКТОРА

"Народ, я чего-то новой рассылки не узрел!
Где же периодичность, блин, а?
Только в мозгах проясняться началось и на тебе :{" (C) Baltika

Хм... получил я вчера почту, посмотрел... и точно - нема там новой рассылки. Абидна, да?

Мне тож обидно. Ну никто не хочет эту гребаную рассылку делать!! Материала - куча, но вот чтобы подорваться и оформить эту дрянь более-менее красиво и связно...

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

Да черт с ними, в общем! Если зарелизим через месяц-другой первый в мире линуксовый/кластерный брутфорсный кракер rar-архивов - сам же потом балтикой и проставляться буду :).

Ладно... проехали...

Новостей у нас 10b. Одна хорошая, одна плохая.

Та, которая хорошая, состоит в том, что мы начали редизайн нашего сайта делать :). Зацените главную страничку :) и логотипчик а-ля Микеланджело :). "Рука бога и рука Адама" :) Ааааа? Лично мне нравится :). Тем более, что HTML-код ручками писался :). Безо всяких визивингов :).

18 килобайт эта страничка весит :)). А прошлый index.html - 35 :). Мне теперь на hi-tech.ph.ru ходить страшно! Зато как приятно на hi-tech.nsys.by :))). Ооооо...

А теперь плохая новость: начать-то мы начали, а вот кончить никак не можем :(. На один Архив рассылки (который еще и на сервер-то даже не залит) времени ушло немеряно! (Ужас какой)... Так что нечего пока и на nsys'е делать :(((.

Для тех кто не понял: МЫ НЕ ПОМЕРЛИ! МЫ ПРОСТО ОКУКЛИЛИСЬ!

Превращение идет. Метаморфоза то бишь. Были червяком мерзким, а теперь типа бабочкой станем. Еще более мерзкой.

Да черт с ней, с "мерзостью"! У бабочки вовсе не красивыЯ пятнышки главное. И вовсе не то, что она пыльцу, а не гадость всякую, жрет! Главное у бабочки - крылья!

Как говаривал Козьма прутков, "если у тебя есть фонтан - заткни его". Так вот: затыкаю свой и переходим к делу.

ПЕЧАТЬ ШЕСТНАДЦАТЕРИЧНЫХ ЦИФЕРЕК:)

[1] Мы уже неоднократно юзали хорошую мнемоническую (aka ассемблерную) команду ADD :). Напомню, что в результате выполнения команд

    mov AX,2
    mov BX,3
    add AX,BX

в регистр AX у нас помещалась сумма (AX=AX+BX).

Мы смотрели на это дело под отладчиком, и, к своей неописуемой радости, убеждались в том, что эта дрянь действительно работает. Но толку нам знать, что она работает?? Программа ведь не только работать должна, но еще и диалог какой-нить между юзверем и компутером обеспечивать! Например, спрашивать у него эти два числа и выплевавать на монитор результат их сложения.

Вот именно - выводить на монитор, а не заносить в какой-то абстрактный регистр.

С клавиатурным вводом пока обождем, а вот с выводом (на монитор) разберемся прямо сейчас.

Как мы это сделаем? Вы уже неоднократно слышали, что "в ассемблере" все делается "ручками" :). Сейчас вы лишний раз убедитесь в том (некоторые замрут в ужасе), что это утверждение истинно. Для вывода значения регистра мы вовсе не "познакомимся с новым прерыванием". Даже такая простейшая операция, как "вывод на дисплей значения регистра (переменной)" - это целая процедура. И не одна, как вы скоро в этом убедитесь. Страшно?

Поехали!!

[2] Задача: вывести на монитор значение регистра DL.

Народ! Давайте сразу расставим границы между КОДОМ СИМВОЛА и его НАЧЕРТАНИЕМ.

Например, у нас F3h в DL. Как мы хотим это вывести? Как символы 'F3' или же как ASCII символ, соответствующий коду F3h? Определяемся. Если в DL у нас F3h - то надо чтоб именно 'F3' у нас на монитор и выводилась. Не 'э перевернутое', не '46 33', а именно 'F3'. Помедитируйте. Уловите разницу между 'э', '46 33' и 'F3'.

Эту задачу мы немножко упростим :). Для начала напишем процедуру, которая выводит только младшую тетраду регистра DL (цифру "3" в нашем примере). Для этого мы обратимся к процедуре WRITE_CHAR из прошлого номера. Именно она печатает нам на монитор символ, ASCII-код которого находится в DL.

Но тут загвоздка: в DL-код, а печатается-то символ :). А нам, собственно, именно две циферки шестнадцатеричного кода, как два символа, и нужно напечатать. Ну, или хотя бы младшую циферку этого кода...

Решается эта задача элементарно :). Главное - это правильно ее сформулировать!

Вот чего я тут нарисовал:

       ЕСТЬ         НУЖНО
    код символ    символ код
    -----------  ------------
    00h  '?'       '0'   30h
    01h  '?'       '1'   31h
    02h  '?'       '2'   32h
    03h  '?'       '3'   33h
    04h  '?'       '4'   34h
    05h  '?'       '5'   35h
    06h  '?'       '6'   36h
    07h  '?'       '7'   37h
    08h  '?'       '8'   38h
    09h  '?'       '9'   39h
    0Ah  '?'       'A'   41h
    0Bh  '?'       'B'   42h
    OCh  '?'       'C'   43h
    ODh  '?'       'D'   44h
    OEh  '?'       'E'   45h
    0Fh  '?'       'F'   46h

Только во второй колонке вместо вопросительных знаков должны быть соответствующие всякие символы (посмотрите в ASCII-таблице, какие они на вид страшные!).

Процедурка наша вот что должна делать:

Всего-навсего перевести "переконвертировать" тетраду в код соответствующего ей символа...

Завернуто??

Если разобраться, то не очень-то и завернуто.

Смотрите: в DL у нас 03h. Хотим мы эту '3' на монитор вывести. Если вызовем WRITE_CHAR, то у нас символ "сердечко" выплюнется. А надо, чтоб символ '3' вывелся, код которого 33h.

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

А теперь обратите внимание, насколько "шестнадцатеричная циферка" (тетрада) отличается от ASCII-кода, этой "циферке" соответствующего. Сам скажу: на 30h для цифр от '1' до '9', и на 37h для цифр от 'A' до 'F'. То есть "переконвертацию" мы запросто можем сделать командами add DL,30h (если тетрада в диапазоне 0...9) и add DL,37h (если тетрада в диапазоне A...F).

Короче, вот код (пропиваю!):

    ;-[WRITE_HEX_DIGIT, V1]----------------------------------
    ;Печатает одну шестнадцатеричную цифру (младшую тетраду DL)
    ;(старшая тетрада должна быть равна 0)
    ;На входе: DL - цифра
    ;На выходе: нихрена
    ;Прерывания: ан нэту
    ;Процедуры: WRITE_CHAR
    ;--------------------------------------------------------
    WRITE_HEX_DIGIT proc
      push DX
      cmp  DL,0Ah
      jae  HEX_LETTER
      add  DL,30h
      JMP  WRITE_DIGIT
     HEX_LETTER:
      add  DL,37h
     WRITE_DIGIT:
      call WRITE_CHAR
      pop  DX
      ret
    WRITE_HEX_DIGIT endp

Сначала, ессно, изменяемые регистры сохраняем (мы ж их изменяем!). (Ну, и восстанавливаем в конце процедуры (PUSH и POP соответственно)).

Потом у нас логическое ветвление организовано. Сравниваем значение DL с "общей границей" наших двух диапазонов (команда - CMP, "граница" - 0Ah). Если это значение больше или равно 0Ah, то прыжок на метку HEX_LETTER, прибавление к DL 37h и печать цифры (WRITE_CHAR). Иначе добавляем 30h и безо всяких условий перепрыгиваем на вызов WRITE_CHAR (минуя add DL,37h то бишь).

Все. Тестируем.

[3] Про тестирование - базар отдельный. В данном случае мы можем запросто проверить нашу процедуру на абсолютно всех возможных значениях этой тетрады (всего-то ничего 16 вариантов). Но намного правильнее, проанализировав алгоритм, установить своего рода "критические" значения, на которых целесообразно проводить проверку. Плюс, естественно, минимальное и максимальное значения.

    TESTING proc
      mov DL,00h
      call WRITE_HEX_DIGIT
      mov DL,01h
      call WRITE_HEX_DIGIT
      mov DL,09h
      call WRITE_HEX_DIGIT
      mov DL,0Ah
      call WRITE_HEX_DIGIT
      mov DL,0fh
      call WRITE_HEX_DIGIT
      call EXIT_COM
    TESTING endp

Если сия "тестовая" (она же - главная) процедура выведет на монитор

    019AF

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

Кто не просто скопировал процедуру из буфера обмена, а действительно разобрался с тем, как она работает - сами знают, что значение старшей тетрады нашей процедуре НЕбезразлично. Оно должно быть равным 0.

[4] Что мы имеем? Процедуру для вывода на дисплей одной шестнадцатеричной циферки - младшей тетрады (в DL). Но нам-то нужно две вывести! Сначала старшую циферку-тетраду, и только потом - младшую!

Таким образом очередная задача разбивается на две части: печать старшей тетрады DL и печать младшей тетрады DL.

Первая "подзадача" решается легко: нужно просто старшую тетраду переместить на место младшей и вызывать процедуру (основательно протестированную и 99,9%-но работающую) WRITE_HEX_DIGIT.

А вторая подзадача - хм... заключается в восстановлении предыдущего (мы ж тетраду перенесли) значения DL и снова - вызове WRITE_HEX_DIGIT.

(Хе! Вот теперь-то вы уж точно почувствуете всю прелесть "дробления кода на процедуры"!)

Перенос тетрады мы осуществим при помощи команды SHR, которую в умных книжках обзывают как "логический сдвига разрядов операнда вправо". Объясню.

Представьте себе деревянную доску, длинной в 8 бутылок пива и шириной в одну. (В принципе, доску эту можно и в два раза длиннее представить, но тогда на ней надо "DX" написать, мы же пока только "DL" напишем). А еще дурня, у которого на лбу SHR написано. Так вот, если этому придурку стукнуть по хребту, то он слева от доски поставит ПУСТУЮ бутылку, а остальные сдвинет на одну позицию вправо, в результате чего самая правая бутылка, ессно, с доски упадет.

Бутылки, которые сразу стояли, могут быть пустыми или полными, а вот дурень SHR - только пустые ставит. И только слева.

    "исходное" 11110011
       SHR     01111001
       SHR     00111100
       SHR     00011110
       SHR     00001111

Ну тут и ежу все понятно. Четыре раза дурню по хребту надо дать, чтоб старшая тетрада на место младшей переместилась.

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

Реализовывается этот сдвиг вот как:

    mov DL,11110011b
    mov CL,4
    shr DL,CL

В DL - наша цепочка битов. (11110011b = F3h, естественно).

В CL заносим "на сколько позиций" нам нашу цепочку сдвинуть.

Ну и SHR - это дурень, который сдвигает вправо, а слева нули дописывает.

[5] Думаете, это все?? Разогнались!! Не все так просто :).

WRITE_HEX_DIGIT у нас требует, чтобы первой тетрадой были только одни нули. Я заострял на этом ваше внимание.

При печати первой тетрады это условие соблюдается. SHR слева нули дописывает.

А вот при печати второй цифры нужно вот что: ничего никуда не сдвигая, обнулить старшую тетраду, а младшую (которая, собственно, и есть "цифра") оставить в покое.

Решим мы эту команду при помощи логической операции "и" (and по-аглицкому). Кто статьи Хемуля в прошлых выпусках читал, сами вспомнят, что это за "и" такое. А кто не читал, я напомню "таблицу истинности" для данной логической операции:

    0 0 1 1
    0 1 0 1
    -------
    0 0 0 1

А теперь и для особо одаренных:

    0 and 0 = 0
    0 and 1 = 0
    1 and 0 = 0
    1 and 1 = 1

Смотрите, интересно как получается:

Если мы AND чего-либо (нуля или единички) с 0 делаем, то у нас в результате 0 и только 0 получается.

А если AND с единичкой - то ЧТО БЫЛО, ТО И ОСТАЕТСЯ.

(Это и есть потаенный дZенский смысл команды AND)

Решение нашей проблемы (обнулить старшую тетраду, а младшую оставить без изменений) таким образом сводится к тому, что старшую тетраду нужно "AND 0", а младшую - "AND 1".

То есть значению DL с 00001111b (оно же - 0Fh) "AND" сделать.

На ассемблере это вот как выглядеть будет:

    and  DL,00001111b

Естественно, 00001111b = 0Fh

Аминь!!

[6] Уфф... Вот что получиться в итоге должно:

    ;-[WRITE_HEX, V1]----------------------------------------
    ;Печатает две шестнадцатеричные цифры
    ;На входе: DL - типа цифры две :))
    ;На выходе: нихрена
    ;Прерывания: ан нэту
    ;Процедуры: WRITE_HEX_DIGIT
    ;--------------------------------------------------------
    WRITE_HEX proc
      push CX
      push DX
      mov  DH,DL
      mov  CL,4
      shr  DL,CL
      call WRITE_HEX_DIGIT
      mov  DL,DH
      and  DL,0Fh
      call WRITE_HEX_DIGIT
      pop  DX
      pop  CX
      ret
    WRITE_HEX endp

Ну че тут объяснять?? Я уже объяснил все!! Единственное, что могу добавить - это про mov DH,DL. Этой командой мы значение регистра копируем перед тем как биты "ему" сдвинуть. А потом статус-кво mov DL,DH восстанавливаем, чтоб и младшую цифру напечатать.

Все. Тестируем. Вроде должно работать.

[7] Так сказать "к вопросу о шаблонах мышления"...

Мы тут доооолго трахались с тетрадами. Вроде успешно.

Когда при тестировании понимаемости материала мы предложили пяти "подопытным" самостоятельно написать процедуру для вывода на монитор "большого" регистра (DX), они все как один начали сдвигать байты... :(

Народ!! Это не есть правильно!!

Не буду разжевывать и в рот класть, "почему".

Вот первый "правильный" способ (для регистра DX):

    call WRITE_HEX
    mov DL,DH
    call WRITE_HEX

а вот второй:

    call WRITE_HEX
    xchg DL,DH
    call WRITE_HEX

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

[8] Ну, и напоследок, - информация к размышлению:

Команду shr можно использовать для деления целочисленных операндов без знака на степени 2 :)).

    mov     cl,4
    shr     ax,cl

Думаете эти "фокусники" который в уме офигенны уравнения считать умеют, шибко умные?? Нифига!! Они просто люди ЗНАЮЩИЕ.

А делить на десять и вы умеете...

Над этим я настоятельно рекомендую дооооолго помедитировать.

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

Впрочем, это (вычисления в уме) - тема отдельная. Мы ее тоже когда-нить коснемся :).

    MATRIX MUST DIE!!

ПРИМЕЧАНИЕ BY DZ^2 ХЕМУЛЬ СОВЕТИКУС (ДЛЯ ОСОБО ПРОДВИНУТЫХ)

    ; In:   AL (value)
    ; Out:  AX (ASCII string, 2 hex digits)
    ; Use:  none
    ; Modf: CL
    ; Call: none

    hexbyte2a proc
      mov  ah,al
      and  al,0Fh
      cmp  al,10
      sbb  al,69h
      das
      xchg ah,al
      mov  cl,4
      shr  al,cl
      cmp  al,10
      sbb  al,69h
      das
      ret
    hexbyte2a endp

В данном случае конвертация отделена от печати, хотя никто не мешает их совместить. Примечание: в результате старшая тетрада размещается в AL - это для того, чтобы потом AX можно было сохранить в память как "строку". Я именно так и делают - сначала подготавливаю в памяти строковые буфера, а потом одним махом вывожу всё.

Такие дела...

ОБЗОР ДZЕНСКИХ САЙТОВ - ZEN.RU

Не поднимается моя грязная (окровавленная, с человеческими кишками, намотанными на пальцы) рука "обозревать" этот сайт. Поэтому скажу просто:

Народ! Те, кто понял, в чем заключается программерский дзен - пора делать следующий шаг!!
Прежде, чем воевать с Матрицей, нужно убить матрицу в себе.
Прежде чем научиться летать, нужно научиться падать.
Все падают в первый раз. Лишь немногие решаются на Вторую Попытку...
www.zen.ru
Школа по Второй Логике.

И вытрите ноги!

АНОНС

В следующем номере - вывод на монитор значений регистров в десятичной и двоичной системах счисления. ЕСЛИ...