Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
1

TAJGA FASM Tutorial

10.09.2014, 12:34. Показов 8923. Ответов 6
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Учебник TAJGA FASM
Перевод TAJGA FASM Tutorial by vid
перевёл S.T.A.S.

Содержание
  1. О туториале
  2. Вступление
  3. Начало работы
  4. Первая программа
  5. Метки, адреса и переменные
  6. Порядок следования байтов и WORD регистры
  7. Переходы и ветвления
  8. Двоичная арифметика
Вложения
Тип файла: rar Fasm Tutorial.rar (44.2 Кб, 242 просмотров)
5
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
10.09.2014, 12:34
Ответы с готовыми решениями:

Вызываю dll (написанную на vc++2008) из Fasm. Через 40 секунд вылет из программы.Без вызова dll из Fasm программа не вылетает.
Программа на vc++2008: #include "MathFuncsDll.h" #include <stdexcept> using namespace std; ...

Tutorial
Добрый день, подскажите, что актуальное почитать для разработки под мак. Читал "Object-C 2.0 и...

Best CSS3 Tutorial
Какую литературу или сайт пересмотреть для начинающего верстальщика?) Спасибо

__asm__ tutorial
__asm__ tutorial нужно вставить немного кода на асме для чтения rdtsc, подскажите пожалуйста...

6
Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
11.09.2014, 12:02  [ТС] 2
1. О туториале

Для кого?

Туториал предназначен для новичков, какие-либо познания в программировании не требуется. Хотя изучение ассемблера в качестве первого языка программирования довольно сложно, это возможно. Необходимо лишь знать как работать в командной строке (command.com в DOS/win95/98/ME, cmd.exe в winNT/2K/XP). Некоторое знание программирования весьма желательно, но не обязательно.

Какая OS?

Я решил написать туториал для DOS, потому что он позволяет использовать компьютер наиболее полно, в отличие от Windos. Я хочу рассмотреть некоторые вопросы которые недоступны в Windos, так что DOS — единственный вариант. Перейти от DOS к Windos не очень сложно, если Вы знаете защищенный режим, так же существуют туториалы Iczelion'а рассказывающие об этом. Лично мне не нравится начинать изучение ассемблера под Windos, из-за того что там некоторые вещи скрыты от нас.

Статус написания туториала

Туториал очень далек от завершения, но содержит достаточно информации для начала.
В настоящее время пишется 7-я глава об арифметических инструкциях.

Дополнительные разделы

В туториал включена так же статья связанная с особенностями FASM:
Правовая информация

Туториал предоставляется без всяких гарантий (Вы используете его на свой страх и риск ). Это так же значит, что Вы можете делать с ним всё, что хотите. Если Вы планируете включить туториал или его часть в свой проект, или перевести его на другой язык, или что-то в этом духе, было бы хорошо написать автору и упомянуть ник vid где-нибудь. Как бы то ни было, если Вам так будет удобнее — распространяйте под своим именем, Вы заработаете какую-нибудь репутацию (возможно, ламера).
Вы можете посодействовать улучшению этого туториала отправляя свои замечания на форум FASM, форум WASM или на e-mail если хотите. Так же, если Вы что-то найдёте непонятным, пожалуйста сообщите об этом, что бы я мог исправить это для будущих читателей.
Оригинальный туториал конвертировал в HTML Decard. Он так же написал утилиту для перевода исходников FASM в HTML с подсветкой синтаксиса.

2. Вступление

Зачем использовать Ассемблер FASM?

На самом деле, не существует никакого стандарта на синтаксис Ассемблера. Возможно найти множество ассемблеров (компиляторов Ассемблера) с весьма разными синтаксисами. Наиболее общепринятый – это MASM, потом TASM с почти одинаковым синтаксисом. Мое мнение – этот синтаксис весьма глупый, но это то, что люди обычно называют Синтаксис Ассемблера. Не плохо бы узнать как минимум его основы, что бы понять чужой код. Но здесь больше не будет упоминания о MASM или TASM.
Еще один распространенный синтаксис имеет NASM. Это очень хороший ассемблер, с подобным FASMу синтаксисом, но он немногим более сложноват, да и макроязык у него похуже. Для действительно больших проектов он, возможно, лучше чем FASM, но существует не так много больших проектов, написанных на любом из ассемблеров. Другой, принятый социумом – это Linux'овый as.
Все, что перечислено выше – сложные ассемблеры с множеством бесполезных возможностей. FASM – маленький, быстрый (быстрейший), свободный (в отличие от некоторых других). Вы можете писать на нем программы быстрее или с той же скоростью как и в других ассемблерах. Когда я впервые увидел FASM, я уже знал MASM/TASM и был поражен, насколько истинен его синтаксис. И, позже, когда я постиг его макроязык, я начал писать код в два раза быстрее чем прежде. Предоставьте ему шанс.

2.1. Использование FASM

Компилятор

Для начала нужно скачать компилятор FASM. Есть несколько версий: для DOS, Win32 консоли, версия Win32 GUI. И для Linuxа — использоваться не будет. Скачайте две версии для windos. Дополнительно можете использовать какое-либо IDE.

Компиляция

В GUI версии, стОит нажать CTRL-F9 и открытый файл откомпилируется (или F9 чтобы еще и запустить его потом). В консольной версии напишите в командной строке следующий текст:
Assembler
1
fasm file.asm file.exe
где file.asm – исходный файл, который хотите откомпилировать, а file.exe – целевой (компилированный) файл. Он может иметь другое расширение, конечно. И не забывайте прописать в PATH путь до файла fasm.exe или используйте какой-нибудь bat файл, или что еще. Учитесь на практике.
3. Начало работы

Здесь я предвижу, что у вас есть некоторые основные понятия о том, что такое байты и кое-какие идеи об ASCII-кодах. Возможно, я расскажу об ASCII в более поздних версиях туториала.

Для начала попытаемся скомпилировать пустой исходный файл. Прямо сейчас создайте пустой файл empty.asm и напишите в командной строке:

Assembler
1
fasm empty.asm empty.bin
Вы должно быть увидите, что создан файл empty.bin и его длина равна 0.
Теперь мы сотворим файл, содержащий кое-какие данные. Создайте текстовый файл содержащий в себе строку:

Assembler
1
db 'a'
и скомпилируйте его (я надеюсь Вы уже поняли, как). Когда посмотрите полученный файл, Вы должны увидеть, что его длина равна 1 байт и он состоит из одного символа a.
Пора проанализировать (?) исходник: db — это директива которая значит определить байт (от английского define byte). Итак, эта директива поместит байт в целевой файл. Значение байта должно следовать непосредственно за директивой. К примеру, db 0 поместит в целевой файл байт со значением 0. Но если Вы хотите ввести таким образом какой-то символ, надо бы помнить его ASCII значение. В таком случае можно поместить символ между апострофами ' или кавычками " и компилятор найдет это значение за Вас. Вот так работает этот код.

Assembler
1
2
3
директива (directive)
Команда компилятору, как и какие данные следует создать в целевом файле. 
Выполняется во время компиляции
Теперь давайте сделаем файл, с большим количеством символов. Это будет:

Assembler
1
2
3
db '1'
db '2'
db '3'
Как это работает, я думаю, понятно, — сохраняет три байта в целевом файле, который сейчас должен содержать простую строку 123. Но, к примеру, нельзя написать:
Assembler
1
db '1' db '2' db '3'
так как каждая директива должна быть на отдельной строке. Но если хотите определить несколько байт, можно просто использовать директиву db и за ней несколько значений, разделенных запятыми ,:
Assembler
1
db '1','2','3'
В результате так же будет файл 123.
Но что, если Вы хотите чего-то большего, к примеру файл содержащий Это моя первая длинная строка в FASM? Можно написать:
Assembler
1
db 'Э','т','о' и так далее
но это не очень красиво. В таком случае, если хотите определить несколько последовательных символов посредством db, можно использовать:
Assembler
1
db 'Это моя первая длинная строка в FASM'
Итак, необходимо поместить весь текст между апострофами (или кавычками). Можно написать и так тоже:
Assembler
1
db 'Это моя первая длинная строка в',' FASM'
или
Assembler
1
db 'Эт','о моя первая дли','нная строка в FASM'
и так далее.
Assembler
1
2
3
4
строка (string)
набор символов
строка в кавычках (quoted string)
строка в исходнике, заключенный между апострофами или кавычками (более строгое название символьной строки)
Не забывайте, что директива db предназначена для определения любых данных, а не только текста, и данные — это большей частью числа. При помощи db можно задавать байты (то есть размер их ограничен восьмю битами). А байты — это числа от 0 до 255. Если хотите бОльшие числа (0..65535), байтов недостаточно. В таком случае люди (?) обычно используют 16 битные числа, называемые слова (word). Для того что бы задать такое число, используется директива dw — define word.
4. Первая программа

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

Assembler
1
2
машинный код (machine code)
набор чисел, которые представляют собой команды процессора
3
Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
11.09.2014, 12:11  [ТС] 3
Теперь уделим внимание ДОСовым программам COM, изредка называемых memory image. Это самые простые выполняемые (запускаемые) файлы в DOS и Windows. Итак, давайте создадим первый COM файл, который ничего не делает:
Assembler
1
2
org 256
int 20h
Скомпилируйте это в COM файл и запустите его. Ничего не должно произойти. Теперь давайте посмотрим, что значат эти 2 строки. (Это будет забавно..)
Assembler
1
org 256
Я не буду сейчас объяснять, что делает эта директива. Только помещайте эту строку в начало каждого COM файла! Она не определяет никаких данных, даже не делает ничего, на что Вы можете сейчас обратить внимание. Мы позже доберемся до этого.
Assembler
1
int 20h
Это инструкция. Инструкция — это команда для процессора, которая хранится в созданном файле в виде одного или нескольких байт. Когда Вы запускаете исполняемый COM файл, процессор "проходит" по нему, декодирует инструкции из машинного кода и делает то, что эти инструкции говорят ему делать. Инструкция int 20h говорит что это конец исполняемого файла. Итак, первая наша инструкция говорит процессору прекратить выполнение, поэтому исполняемый файл ничего не делает, как Вы и видели.
Assembler
1
2
инструкция (instruction)
одиночная команда процессора
Кстати — int 20h это не инструкция для процессора, которая заканчивает выполнение программы COM. Она говорит процессору вызвать какую-то системную процедуру. Системная процедура определяется по номеру следующему за int, в данном случае — 20h, что и значит — закончить выполнение COM файла. После int может быть другое число, тогда будет вызвана другая системная процедура. Но сейчас мы можем отвлечься от этого, забыть об этом, и принять int 20h как инструкцию остановить программу.
Итак, машинный код — это набор инструкций. Следует различать директивы и инструкции. Инструкция определяет данные, которые определяют что процессор будет делать во время выполнения. К примеру db 0,0 — директива задающая два нулевых байта, но это еще и инструкция, потому что 2 нуля имеют специальное значение для процессора (пока оставим значение без внимания). org 100h — это директива, а не инструкция, так как она не определяет никаких данных. Вы вникните в это на практике.
Инструкция int 20h — простая, ей не надо никаких аргументов. Но что, если какой-то инструкции нужны аргументы? Для таких случаев процессор имеет свои собственные переменные. Эти переменные называются регистры. Первые регистры, которые мы узнаем — это al, ah, bl, bh, cl, ch, dl, dh, их размер равен байту (они могут содержать значение от 0 до 255).
Assembler
1
2
регистр процессора (CPU register)
внутренняя ячейка памяти процессора
Кстати, int 20h принимает аргумент в регистре al, но, опять же, мы можем отвлечься от этого. И, фактически, значение 20h — тоже аргумент инструкции, но мы отдалились от этого ранее. Это как раз то, что я имел ввиду, когда писал это будет забавно..
Как же тогда установить значение регистра? Для этого есть инструкции, к примеру:
Assembler
1
mov al,10
Эта инструкция устанавливает значение регистра al в 10. mov значит поместить. Место назначения такого помещения указывается вслед за mov (через пробел(ы)), в нашем случае — это регистр al. Затем следует источник для помещения, отделенный запятой, в нашем случае это число 10. Итак, эта инструкция помещает значение 10 в регистр al. Источник и место назначения должны быть одного размера, мы поговорим об этом позже.
Другой пример:
Assembler
1
mov al,bl
Это копирует значение регистра bl в регистр al. И не меняет значение регистра bl. Источник для mov никогда не меняется.
ПРИМЕЧАНИЕ: Вы часто (постоянно?) будете видеть, как люди говорят об инструкции mov. Но mov — не инструкция, и int тоже. mov al,bl и int 20h — это примеры инструкций. mov и int называются мнемоники инструкций. Но согласитесь, все называют их инструкциями, и Вы, вероятно, будете вскоре тоже (и я, наверное, тоже, к сожалению . Аргументы инструкций (части инструкций без мнемоники, такие как al и 10 в mov al,10) называются операндами инструкции (или аргументами инструкции
Assembler
1
2
мнемоника инструкции (instruction mnemonics)
операнд инструкции (instruction operand)
Теперь перейдем к использованию регистров. Мы будем использовать инструкцию int 20h, которая делает много всего в зависимости от значения в регистре ah. Не будем изучать все значения, пока поговорим о значении 2. Когда при выполнении инструкции int 20h это значение находится в регистре ah, то символ из регистра dl (более точно: символ с ASCII значением в dl) выводится на экран (консоль).
ПРИМЕЧАНИЕ: если используете какую-нибудь Windows и файловый менеджер (вроде Total Commander), то увидите окно, которое появляется и сразу исчезает. Но ваш символ отображается в этом окне, и Вы вероятно не можете это заметить. Необходимо запустить DOS-оболочку (cmd в 2K/XP, command в более старых windows) и запускать программу из нее (лучше попробуйте FAR). В любом случае, если Вы не можете справиться с этим, забудьте о ассемблере на некоторое время, и подучитесь пользоваться операционной системой. Но потом, не забудьте вернуться к ассемблеру!
ОК. Итак давайте взглянем на программу выводящую символ "a":
Assembler
1
2
3
4
5
org 256
mov ah,2
mov dl,'a'
int 21h
int 20h
Итак, анализ:
Assembler
1
mov ah,2
устанавливаем значение регистра ah равным 2, это должно быть понятно.
Assembler
1
mov dl,'a'
это помещает символ "a" в регистр dl. Фактически, в Ассемблере нет ничего подобного символу "a". Вы должны были заметить, я написал, что регистр может содержать значения. Ничего о символах. Это работает, так как компилятор преобразует символ в кавычках в его числовое (ASCII) значение которое распознается int 21h как код для этого символа. В Ассемблере символ a значит ASCII-код для символа "a"
Assembler
1
int 21h
В нашем случае, когда ah содержит значение 2, это выводит на экран символ из dl
Assembler
1
int 20h
И мы не можем забывать о прекращении выполнения. Иначе, скорее всего, программа завершится аварийно.
ПРИМЕЧАНИЕ: в Ассемблере символ, заключенный в кавычки - то же самое, что и ASCII-код для этого символа.
Итак, вывод нескольких символов "ab":
Assembler
1
2
3
4
5
6
7
org 100h
mov ah,2
mov dl,'a'
int 21h
mov dl,'b'
int 21h
int 20h
Мы не должны помещать 2 в ah снова для второго int 21h, потому что значение 2 сохраняется. Значение в dl также сохраняется, так что код:
Assembler
1
2
3
4
5
6
7
org 256
mov ah,2
mov dl,'a'
int 21h
int 21h
int 21h
int 20h
выведет "aaa"

5. Метки, адреса и переменные

ОК, давайте перейдем к переменным. В предыдущей главе я писал, что переменные — это общий термин для места, где хранится какое-либо значение. Регистры, к примеру, тоже переменные. Но количество регистров ограничено. (очень ограничено, каких-то 8 + несколько специальных) и их практически всегда недостаточно. По этой причине используется память RAM

ПРИМЕЧАНИЕ: когда говорят о переменных, обычно имеют ввиду переменные в памяти.
5.1. Метки
Проблема заключается в том, что нужно знать где в памяти хранится какое-то значение (переменной). Позиция в памяти (называемая адрес) представляет собой число. Но достаточно сложно помнить это число для каждой переменной.
Assembler
1
2
адрес (address)
число показывающее позицию в памяти
ПРИМЕЧАНИЕ: упрощенно можно принять следующее: вся память компьютера разделяется на ячейки (каждая ячейка памяти — один байт). Эти ячейки нумеруются: 0,1,2,.. номер ячейки — это и есть ее адрес
Другая проблема с адресами — когда Вы изменяете свою программу, адрес тоже может измениться, и придется корректировать этот номер повсюду где он используется. Исходя из этих соображений, вместо реальных адресов используют метки. Метка — всего лишь какое-то слово (не строка, она не заключается в кавычки), которое в программе исполняет роль адреса в памяти. Когда Вы компилируете программу, каждая метка заменяется соответствующим адресом. Для написания метки используются: символы латинского алфавита a .. z, A .. Z), цифры 0 .. 9, знак подчеркивания "_" и точка ".". Но первый символ метки не должен быть числом или точкой. Так же, имя метки не может совпадать с директивой или инструкцией (мнемоникой инструкции). В FASM'е метки чувствительны к регистру ("a" не то же, что "A").
Примеры меток
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
name    метка
a   метка
A   метка, отличная от a
name2   метка
name.NAME2  метка
name._NAME2 метка
_name   метка
_   метка
.name   не метка, так как начинается с точки (метки начинающиеся с точки имеют специальное предназначение в FASM, мы изучим его позже)
1   не метка, так как начинается с цифры
1st_name    не метка по той же причине
name1 name2 не метка, так как содержит пробел
mov не метка, так как mov - это мнемоника инструкции
Assembler
1
2
метка (label)
буквенно-цифровой заменитель адреса
Вы можете использовать метку в точности так же как обычное число. Это число может представлять собой не обязательно адрес

Можно дать определение метки используя директиву label. После этой директивы нужно указать метку (имя метки). Например:
Assembler
1
2
3
4
label name  определение метки, определяет метку name
label _name определение метки, определяет метку _name
label label это не является определением метки, 
так как директива label не может быть именем метки
2
Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
11.09.2014, 12:16  [ТС] 4
Все это определяет метки, означающие адреса данных, определяемых следом за этими директивами.
Assembler
1
2
директива label
определение (описание) метки
Синтаксис:
Assembler
1
label   name
Более простой задать метку — просто написать ее имя и за ним двоеточие ":"
Assembler
1
2
name:
_name:
Но пока мы не будем использовать эту форму.

5.2. Определение переменных

Теперь можно вернуться к проблеме переменных: как определить переменную в памяти. Ваша программа скомпилированная в машинный код для выполнения загружается в память, где процессор выполняет ее инструкция за инструкцией.
Взгляните на это:
Assembler
1
2
3
4
org 100h
mov al,10
db  'this is a string'
int 20h
Эта программа, скорее всего, вызовет сбой, так как после того, как процессор выполнит mov al,10 он наткнется на строку. Но в программе в машинных кодах нет разницы между строками и инструкциями. Все они компилируются в массив цифровых значений (байты). И для процессора нет способа различить, что представляют собой эти байты — строку или инструкцию. В данном примере, процессор начнет выполнять инструкции, числовые значение которых (в машинном коде) соответствуют ASCII-значениям символов строки 'this is a string'.
Теперь взгляните на это:
Assembler
1
2
3
4
org 100h
mov al,10
int 20h
db  'this is a string'
Эта программа будет работать нормально, так как перед тем как процессор достигнет байтов строки, выполнится инструкция int 20h, которая завершит выполнение программы. Так что байты строки не будут выполнены, а просто будут занимать какое-то место в программе. Вот так и можно определять переменные — задать какие-то данные в том месте, где процессор не будет пытаться их выполнять (после int 20h, в нашем варианте).
Итак, программа, где есть переменная размером один байт и значением 105:
Assembler
1
2
3
4
org 100h
mov al,10
int 20h
db  105
Последняя строка определяет переменную, содержащую 105.
Теперь, как же получить доступ к переменной? Для начала необходимо знать ее адрес. Для этого можно использовать метку (про это рассказывалось ранее, перечитайте, если забыли):

Assembler
1
2
3
4
5
org 100h
mov al,10
int 20h
label   my_first_variable
db  105
Сейчас мы уже знаем адрес переменной. Он представлен меткой my_first_variable.
Как добраться до содержимого переменной? Возможно, Вы думаете, к примеру так:

Assembler
1
mov al, my_first_variable
но нет! Вспомните, я говорил, что метка (my_first_variable в данном случае) принимает значение адреса. Так что эта инструкция поместит адрес переменной в регистр al, но не ее содержимое. Чтобы получить содержимое переменной или любого другого места в памяти нужно заключить его адрес в квадратные скобочки [ и ]. Итак, чтобы скопировать значение нашей переменной в регистр al, используем:
Assembler
1
mov al, [my_first_variable]
Теперь определим две переменные:
Assembler
1
2
3
4
5
6
7
org 100h
; какие-то инструкции
int 20h
label   variable1
db  100
label   variable2
db  200
Чтобы скопировать значение из variable1 в al мы используем:
Assembler
1
mov al, [variable1]
А если нужно скопировать al в variable1 тогда:
Assembler
1
mov [variable1], al
Чтобы установить значение variable1 (точнее: чтобы установить значение переменной, адрес которой определяется меткой variable1) равным 10 можно попробовать:
Assembler
1
mov [variable1], 10
но это приведет к ошибке (проверьте, если хотите).
Проблема в том, что мы знаем, что изменяем значение переменной по адресу variable1 на 10, но каков размер переменной? В предыдущих двух случаях был определенный размер — байт. Так как использовался регистр al, размер которого равен байту, то компилятор решил, что и переменная находящаяся по адресу variable1 имеет размер один байт. Чтобы разрешить существующую неопределенность воспользуемся операторами размера. Сейчас будем говорить о двух таких операторах: byte и word. Можно помещать такие операторы перед операндом инструкции, чтобы компилятор мог знать размер переменной:
Assembler
1
mov byte [variable1], 10
Другой вариант:
Assembler
1
mov [variable1], byte 10
В этом случае компилятор знает, что помещаемое значение 10 имеет размер один байт, так что решает, что и переменная — то же.
Но может быть сложно постоянно запоминать и указывать размер переменных, когда их используем. Для таких целей можно установить размер переменной при ее определении. Всего лишь написАть оператор размера после имени метки:
Assembler
1
2
label   variable1 byte
db  100
или
Assembler
1
2
label   variable1 word
db  100
теперь, при использовании [variable1] будет иметь то же значение, что и byte [variable1] (или word [variable1] во втором примере). Так что mov [variable1], 10 будет работать — в первом случае сохранит байт 10 по адресу variable1, во втором — слово, со значением 10.
Assembler
1
2
3
оператор размерности (size operator)
служит для явного указания размера операнда
примеры: byte word
Нельзя использовать в одной инструкции переменные и значения разного размера:

Assembler
1
mov byte [variable1], word 10
или
Assembler
1
2
3
4
mov byte [variable1], word 10
;.....
label   variable1 word
dw  0
Нельзя использовать в одной инструкции 2 области памяти (за исключением некоторых специальных инструкций). Это не правильно и не будет скомпилировано:
Assembler
1
mov [variable1], [variable1]
делайте так:
Assembler
1
2
mov al, [variable1]
mov [variable1], al
Это будет вызывать некоторые проблемы в начале, но заставит в дальнейшем писать более быстрый код. А это основной резон использовать ассемблер.
ПРИМЕЧАНИЕ: оператор размера, заданный при определении метки имеет меньший приоритет, чем при использовании в инструкции, так что здесь будет использован байт:
Assembler
1
2
3
mov byte [variable], 10
label   variable word
dw  10
в то время как в обычном случае — слово (word):
Assembler
1
mov [variable], 10
Я думаю Вы обратили внимание, что использование двух строк для определения одной переменной — многовато. Вот более короткий вариант:
Assembler
1
variable1   db 10
это то же, что и:
Assembler
1
2
label   variable1 byte
db  10
Заметьте, что размер переменной при этом тоже задается. Вообще, если определение данных (с использованием директив db или dw) начинается с метки, то это определяет так же и саму метку, присваивая ей размер определяемых данных. Так же это работает и для слов:
Assembler
1
variable2   dw 10
Пример использования переменных:

Assembler
1
2
3
4
5
    mov ah, 2
    mov dl, [character_to_write]
    int 21h
    int 20h
character_to_write  db 'a'
5.3. Адреса и основы сегментации

Теперь поговорим об адресах подробнее. Я уже говорил, что адрес — это число (!) которое определяет какую-то позицию в памяти. Мы научились представлять эти числа метками, так что теперь числовые значения адресов — забота компилятора. Но Вы до сих пор ничего не знаете о формате этих чисел. Я попытаюсь разъяснить это в данной главе.
Как вы возможно знаете, данные в памяти хранятся в виде битов, которые могут иметь значение 0 или 1. Можно рассматривать память как одномерный массив битов. 8 идущих друг за другом битов образуют один байт. Адрес — это номер (индекс, позиция в массиве) байта. К примеру адрес 0 — это адрес первого бита в памяти (или адрес первого байта), адрес 1 — это адрес восьмого бита (или адрес второго байта) и так далее.
Адреса в COM-файлах — это числа размером в слово, так что
Assembler
1
2
3
label   var1
; какие-то данные
mov al, var1
не верно. Это может работать если var1 меньше 256, то есть помещается в байтовый регистр. Но в общем случае хранить адреса надо в переменных размером word, мы поговорим об этом чуть позже.
Теперь некоторый пример с адресами. Исследуйте этот файл:

Assembler
1
2
3
4
5
6
label   variable1
db  10
label   variable2
db  20
label   variable3
db  30
Здесь адрес соответствующий variable1 = 0, variable2 = 1, variable3 = 2.
ОК. Это выглядит красиво, но это совсем не правда. Проблема в том, что обычно, в одно и тоже время, в памяти загружено много программ (операционная система, драйвер мыши, Ваша программа и так далее). И в этом случай программе необходимо знать, в каком месте памяти она загружена, чтобы она могла добраться до своих переменных. По этой причине адреса являются относительными. Это значит, что для каждой загруженной в память программы резервируется некоторая область, называемая сегмент. Все адреса в памяти, к которым обращается эта программа, в этом случае зависят от начала этой области. Так, [0] не значит первый байт памяти, это значит первый байт сегмента.

Код
сегмент (segment)
непрерывная область памяти, описываемая селектором
Как это работает? У процессора есть несколько специальных регистров (называемых сегментными), которые хранят селектор — число определяющее адрес сегмента (адрес первого байта сегмента). Каждый раз, когда Вы обращаетесь к памяти в своей программе, содержимое этого регистра прибавляется к адресу, указанному Вами, так mov al, [0] получит доступ к первому байту Вашего сегмента.

ПРИМЕЧАНИЕ: Я говорил, что адреса в COM-файле — word'ы. Это значит, что они находятся в диапазоне 0..65535. Так, максимальный размер сегмента = 65536 байт. Это можно обойти меняя содержимое сегментных регистров, но не придавайте этому особого значения сейчас.

ПРИМЕЧАНИЕ: Сегмент — это регион памяти. Но термин сегмент часто используется для адреса начала этого региона. Печально, но факт.

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

Код
офсет (offset)
смещение в байтах от начала сегмента
ПРИМЕЧАНИЕ: Я говорил, что метки представляют собой адреса переменных. Фактически, метки в FASM'е — это смещения переменных. Вот почему FASM = Flat Assembler (Вы осмыслите это позже (намного позже )
Я не буду углубляться в сегментные регистры, в то, как хранится в них адрес начала сегмента (здесь есть разница). Примите их как "черный ящик" пока — это работает и мы можем не обращать внимание на них.
1
Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
11.09.2014, 12:26  [ТС] 5
5.4. Объяснение директивы org

Когда Ваша программа загружена, ей, как правило, нужна какая-то внешняя информация (информация из программы, которая ее запустила). Лучший пример — аргументы в командной строке, или необходимость знать, кто нас запустил. Конечно, эти данные должны находиться в том же сегменте, что и программа. В COM-файлах все это, предоставленное программе DOS'ом, хранится в первых 256 байтах сегмента. Так что программа загружается по смещению 256.

ПРИМЕЧАНИЕ: 256-ти байтная структура перед началом COM-файла называется PSP — program segment prefix (префикс программного сегмента)
Теперь, представим такую COM программу:
Assembler
1
2
3
    mov al, [variable1]
    int 20h
variable1   db 0
(заметьте - нет директивы org 100h). Инструкция mov al, [variable1] занимает три байта, int 20h — два байта, так что variable1 будет означать смещение 5. Значит, инструкция mov al, [variable1] эквивалентна mov al,[5]. Итак эта инструкция обращается к шестому байту сегмента (первый байт - по смещению 0). Но я только что говорил, что в первых 256 байтах в начале сегмента хранится кое-какая информация, и программа загружается после них, по смещению 256. Так что мы не хотим, чтобы variable1 = 5, нам надо 256+5. Как раз это и делает директива org. Она устанавливает начало адресов файла. org 100h скажет FASMу прибавлять 256 к смещениям каждой переменной, определяемой после этой директивы (до следующей org). И это как раз то что нужно для COM-файлов
Так, программа выше будет обращаться не к нужной переменной, а куда-то в PSP (первые 256 байт сегмента). Чтобы она работала правильно, используем:
Assembler
1
2
3
4
    org 100h
    mov al, [variable1]
    int 20h
variable1   db 0
ПРИМЕЧАНИЕ: org влияет на значения меток при их определении (например, label variable byte or variable db 0), но не при использовании (mov al, [variable1]). Это значит, что если Вы измените начальный адрес (origin) директивой org после определения какой-либо метки, то метка не изменит своего значения - независимо, где она будет использована - до или после org.

Я не буду говорить о данных в PSP, пока не стоит о них беспокоиться.

Вы уже должны иметь кое-какое достаточно точное представление о байтовых переменных. Вы уже знаете, что они состоят из 8 бит (сейчас это не так важно) и они могут принимать значение от 0 до 255. О word переменных Вы знаете, что они имеют размер 16 бит и принимают значения от 0 до 65535.

Заметили Вы или нет - word – это 2 байта. Теперь давайте подумаем, как хранить какое-либо значение в этих двух байтах. Каждый байт может быть от 0 до 255. Комбинируя их значения, мы получим 256*256, то есть 65536 возможных значений. Но как же эти 65536 значений хранить в 2-х байтах? Предположим, один из байтов (байт №1) равен 0. Тогда в другой байт (байт №2) можно записать значения 0..255. Так мы храним значения от 0 до 255. Когда байт №1 равен 1, мы можем хранить другие 256 чисел - от 256 до 511. Когда в байте №1 находится число 2, получаем следующие 256 чисел от 512 до 767 и так далее. Итого 256*256, как я и говорил, 65536. Это как в десятичных числах: каждая цифра принимает значения от 0 до 9, а истинное их значение зависит от позиции. Крайняя цифра – от 0 до 9, следующая (? предыдущая?) цифра 10*(0..9), далее – 100*(0..9) и так далее. Тоже самое в word: в одном из байтов – 0..255, в другом – 256*(0..255). Тот байт, в котором хранятся значения 0..255 называется младший байт, другой (где хранятся значения 256*(0..255)) – старший байт.

Assembler
1
2
3
4
младший байт (low-order byte)
наименее значимый байт
старший байт (high-order byte)
наиболее значимый байт
Примеры: (значение word = старший байт : младший байт)

Assembler
1
2
3
4
5
6
7
8
9
0     =   0 : 0
1     =   0 : 1
255   =   0 : 255
256   =   1 : 0
257   =   1 : 1
511   =   1 : 255
512   =   2 : 0
513   =   2 : 1     (513 / 256 = 2, 513 mod 256 = 1)
65535 = 255 : 255   (65535 / 256 = 255, 65535 mod 256 = 255)
Остается последняя проблема: порядок следования этих байтов. То есть, какой из них первый - старший байт или младший байт?. Для разных компьютеров этот порядок различается. Для IBM PC (и совместимых) младший байт – первый, за ним идет старший байт. К примеру:

Assembler
1
2
label   variable1 word
dw  0
значит byte [variable] – это младший байт, а byte [variable + 1] – старший. (Прибавлением 1 к смещению метки variable занимается компилятор. Это значит следующий байт после смещения равного variable, я надеюсь достаточно понятно).
ПРИМИЧАНИЕ: Когда младший байт идет первым, это называется little endian encoding, когда первым идет старший - big endian encoding, но эти термины не особо важны для начинающих ассемблерщиков.

6.2. WORD регистры

Помимо байтовых регистров (таких как al,ah,dl...), в процессоре, конечно, есть также WORD регистры. Вы знаете, что слово (WORD) – комбинация из 2-х байтов, так же и с регистрами. WORD регистры – это комбинация байтовых регистров. Первые WORD регистры, которые мы изучим – это ax, bx, cx и dx.

ax – комбинация из al и ah. al - это младший байт, ah – старший. Так же: bx = bh:bl, cx = ch:cl, dx = dh:dl. (обратите внимание на вторую букву в названиях байтовых регистров L=LOW младший, H=HIGH старший). Если хотите эмулировать регистр ex в памяти, то это будет так:

Assembler
1
2
3
label   ex word
el  db 0
eh  db 0
el должен быть младшим байтом, та что он идёт первым.
Assembler
1
2
регистр слова (word register)
имеет размерность 2 байта или 16 бит, способен вместить числа 0..65535
Assembler
1
ax, bx, cx, dx
ПРИМЕЧАНИЕ: Буквы a, b, c, d – исторические сокращения от accumulator, base, counter и data и не имеют никакого отношения к алфавитному порядку. Реальный порядок регистров: ax, cx, dx,bx, но он не важен до тех пор пока не захотите сами генерировать или менять машинный код.

Теперь, если хотите установить значение регистра ax в 52, используйте:

Assembler
1
mov ax, 52
но так же возможно:
Assembler
1
2
mov al, 52
mov ah, 0
Установить dx в 12345:
Assembler
1
mov dx, 12345
но можно и так (хоть и нет резона это делать в реальных программах - это лишь иллюстрация отношения байтов):
Assembler
1
2
mov dh, 48
mov dl, 57
потому что 48 - это целая часть от 12345 / 256, а 57 – остаток от 12345 / 57.
ПРИМЕЧАНИЕ: Вы знаете, что операнд инструкции может быть числом (числовой константой), такой как 0, 256, 12345 и так далее. Но все Ассемблеры, которые я знаю позволяют использовать так же какое-либо арифметическое выражение. Во время компиляции это выражение будет вычислено и заменено его результатом. Так, mov dx,(1 + 5) – тоже, что и mov dx, 6. Или проще, код выше может быть записан:

Assembler
1
2
mov dh, 12345 / 256
mov dl, 12345 mod 256
Символ / - оператор целочисленного деления, mod – остаток от целочисленного деления (modulo). Вам не обязательно знать эти операторы сейчас, но в любом случае желательно иметь представление о выражениях. Допустимы лишь те выражения, значения которых можно вычислить в процессе компиляции программы, то есть в них нельзя использовать регистры и переменные в памяти, так как их значения определены лишь во время выполнения программы.
В процессоре есть также другие WORD регистры: sp, bp, si, di. Но нельзя непосредственно обращаться к их половинкам, можно только к целому слову. Это ограничение процессора, тут уж его не поделаешь. К примеру, если хотите установить старший байт регистра si в 17, нужно (?) делать так:

Assembler
1
2
3
mov ax, si
mov ah, 17
mov si, ax
Итак, сначала копируем значение si в ax. В старший байт регистра ax (то есть в регистр ah) можно записать значение. При этом младший байт сохранится. Потом копируем значение ax обратно в si. Старший байт изменился, младший – нет.
ПРИМЕЧАНИЕ: регистр sp всегда, а bp обычно имеют специальное предназначение в коде сгенерированном большинством (всеми?) не-ассемблерными компиляторами. Регистры si and di можно использовать как хотите.

6.3. Вывод строки с использованием int 21h ah=9

Это должно бы быть частью 3-й главы об адресах, но Вам нужно было бы знать регистр dx, про который рассказано здесь..

Здесь мы будем говорить о еще одном использовании int 21h. Вы уже знаете, что когда в ah содержится 2 то int 21h выводит символ из dl на экран. Но если мы хотим отобразить какой-то более длинный текст, нам придется устанавливать dl для каждого символа и это кривой способ. Не лучше ли просто хранить желаемую строку где-то, и потом просто отображать ее?

Для этих целей мы можем использовать int 21h записав 9 в ah и адрес строки - в dx. Что-то вроде:

Assembler
1
2
3
mov ah, 9
mov dx, address_of_string
int 21h
Но появляется другая проблема – как определить длину строки, то есть количество символов, нужное для отображения. Для этого существует несколько способов, мы будем говорить о самом простом - используемом в int 21h/ah=9. Существует специальный символ, предназначенный для использования в качестве маркера конца строки. В нашем случае - это символ $. Итак, чтобы сохранить строку Hello World, используем Hello World$, где $ значит – конец строки.
Пример вывода строки на экран:

Assembler
1
2
3
4
5
6
7
    org 100h
    mov ah, 9
    mov dx, text_to_display
    int 21h
    int 20h
label   text_to_display
    db  'Hello World$'
Эта программа выведет на экран:
Assembler
1
Hello World
1
Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
11.09.2014, 12:42  [ТС] 6
У такого метода маркировать конец строки есть одно ограничение – нельзя отобразить символ $. К примеру:
Assembler
1
2
3
4
5
6
7
    org 100h
    mov ah, 9
    mov dx, text_to_display
    int 21h
    int 20h
label   text_to_display
    db  'It costed 50$, maybe more$'
несомненно выведет на экран только лишь:
Assembler
1
It costed 50
Это можно обойти так:
Assembler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    org 100h
    mov ah, 9
    mov dx, text1
    int 21h
    mov ah, 2
    mov dl, '$'
    int 21h 
    mov ah, 9
    mov dx, text2
    int 21h 
    int 20h
label   text1
    db  'It costed 50$'
label   text2
    db  ', maybe more$'
Первая часть (первый int 21h) выведет It costed 50, затем int 21h/ah=2 выведет $ и второй int 21h/ah=9 допишет , maybe more. Мы не будем пока уделять больше внимания этому ограничению, оно упоминалось для полноты картины.
Подробнее об int 21h/ah=9. Как Вы, вероятно, уже поняли, это выводит на экран каждый символ (точнее - каждый символ, чей ASCII-код содержится в байте) от адреса в dx до первого символа $ после адреса в dx.

ASCII коды от 0 до 31 (я так думаю) имеют специальное значение для int 21h/ah=9. Эти коды имеют соответствующие им символы (смайлики и так далее), но int 21h/ah=9 не отображает их, а делает кое-что другое. К примеру символ с ASCII кодом 7 производит короткий звуковой сигнал. Попробуйте:
Assembler
1
2
3
4
5
6
7
    org 100h
    mov ah, 9
    mov dx, text
    int 21h
    int 20h
label   text
    db  'Beep',7,'$'
Это отобразит Beep и издаст этот самый Beep.
Другие обычные значения – это 10 и 13. 10 перемещает курсор в первую позицию текущей строки. 13 перемещает курсор на строку вниз (при достижении нижней границы экрана, он скроллируется вверх). Так что, комбинация их переместит курсор в начало следующей строки. Это должно (но не всегда) работать при любом их порядке, но лучше 13 всегда писать первым. Эти два символа часто называют EOL. Вот например:
Assembler
1
2
3
4
5
6
7
    org 100h
    mov ah,9
    mov dx,text
    int 21h
    int 20h
label   text
    db  'Line 1',13,10,'Line 2$'
Это должно вывести на экран:
Assembler
1
2
Line 1
Line 2
ПРИМЕЧАНИЕ: ASCII код 13 называется CR, а 10 – LF.

Другой пример с адресами, но с использованием WORD регистров. Проверьте себя - осмыслили ли вы третий раздел:
Assembler
1
2
3
4
5
6
    org 100h
    mov ah, 9
    mov dx, [address_of_text]
    int 21h
text    db  'Hello World$'
address_of_text dw text
Здесь мы помещаем в регистр dx содержимое переменной address_of_text, где хранится значение text. Как мы знаем, text представляет собой офсет строки Hello World$. Таким образом, загружая в dx содержимое address_of_text мы поместим туда офсет строки которую хотим вывести на экран. Я надеюсь Вы это поняли.
7 - Переходы и ветвления

Вы должны уже знать немного о том, как инструкции выполняются процессором. Он берет машинный код инструкции, выполняет его, и переходит к следующей инструкции. Это продолжается до тех пор, пока не встретится инструкция int 20h. В этой главе мы изучим кое-что об инструкциях меняющих такой ход вещей.

7.1. Счётчик команд

Процессор загружает первую инструкцию (он знает из скольких байтов она состоит), выполняет ее и переходит к другой инструкции. Но как работает этот механизм? В процессоре есть специальный WORD регистр ip, где хранится адрес исполняемой инструкции. После того, как инструкция выполнится, процессор прибавляет ее размер к ip и исполняет инструкцию по адресу [ip]. Упрощённо, механизм работает примерно так:
  • Цикл:
    • Читаем инструкцию из [ip]
    • size = размер инструкции в ip
    • ip = ip + size
    • Выполняем текущую инструкцию
  • пока не нашли int 20h повторяем Цикл
ПРИМЕЧАНИЕ: подобно другим указателям на адрес, ip не хранит полный адрес инструкции, только смещение.

Код
Счётчик команд (instruction pointer)
Регистр процессора, в котором хранится офсет команды, 
следующей за выполняемой в данный момент
7.2. Переходы

Регистр ip не похож на остальные регистры (такие как ax,ax,bp...). Его содержимое нельзя изменить используя инструкцию mov. mov ip,5 не будет работать. Но есть специальные инструкции, способные менять этот регистр. Это jmp (от jump - прыжок, скачок). У этой инструкции должен быть один операнд, новый адрес для регистра ip. Так jmp 5 имеет тот же эффект как и mov ip,5 (если б такая инструкция была). Например:
Assembler
1
2
3
4
5
6
7
8
org 100h
    jmp Start
text    db 'Text to output'
Start:
    mov ah, 9
    mov dx, text
    int 21h
    int 20h
Первая инструкция устанавливает значение ip на адрес инструкции mov ah,9 (адрес определяется меткой Start). Соответственно процессор не будет пытаться выполнить байты строки Text to output и программа будет работать нормально.
ПРИМЕЧАНИЕ: разумеется, когда ip меняется инструкцией jmp, то размер этой инструкции к нему не прибавляется.

Если быть более точным jmp 5, не копирует в регистр ip число 5, подобно инструкции mov. На самом деле к текущему значению ip (то есть к адресу инструкции следующей за jmp) прибавляется или отнимается определенное число – смещение от текущего адреса до адреса 5. Это смещение вычисляется Ассемблером при компиляции, и на этом пока можно не заострять особого внимания. Просто имейте это ввиду; знание этого факта, возможно, облегчит Вашу жизнь в будущем. Такой переход называется относительным. Помимо этого существуют и абсолютные переходы когда в ip загружается значение, например, из другого регистра.

7.3. Сравнения и условные переходы

Если Вы умеете программировать на каком-то языке, должно быть знаете о ветвлении, то есть выполнении частей программы по определенному условию. К примеру: скажем, нам необходимо получить значение не больше, чем 10 в al. Тогда если значение в al > 10, нужно установить al в 10. Это и есть ветвление – если какое-то условие выполняется, то что-то исполняется, иначе – нет. Реализация этого на Ассемблере: когда условие не выполняется, перепрыгиваем через условный кусок кода, иначе (условие выполняется) - просто продолжаем выполнение. Это как код С:
C
1
2
if (condition)      // если условие верно, выполняем следующий код
  ConditionalCode();    // это может быть любой C код, не только вызов функции
переписать следующим образом:
C
1
2
3
if (!condition) goto LabelAfterConditionalCode;
  ConditionalCode();
LabelAfterConditionalCode:
Первая проблема – как решить, что условие выполняется. В Ассемблере, есть инструкции, способные сравнить два операнда. Это cmp. Операнды подчиняются тем же правилам, что и для инструкции mov (это справедливо практически для всех инструкций).
Примеры сравнения:
Assembler
1
2
3
4
cmp ax, bx      ; сравниваем значение ax с bx
cmp al, byte [Label]    ; сравниваем al с байтом по адресу Label
cmp ax, 5       ; сравниваем ax с числом 5
cmp ax, a       ; неправильно, операнды имеют разный размер
Эта инструкция проверяет является ли первый операнд равным, больше или меньше, чем второй.
Assembler
1
инструкция cmp
ОК, мы можем сравнивать, но как хранятся результаты сравнения? CPU имеет специальный регистр, называемый регистр флагов, где и хранятся результаты сравнений (и некоторые другие вещи). К этому регистру нельзя получить доступ используя mov или подобную инструкцию (так же, как и ip), его значение устанавливается инструкцией cmp. Пока нет нужды углубляться в детали, как хранится результат сравнения в этом регистре, для этого потребуется понимание двоичной арифметики.
Assembler
1
регистр флагов (признаков) flags
ОК, мы можем сравнивать, мы знаем, что результат хранится в регистре flags. Единственно, что теперь нужно – собственно условный переход. Условный переход – переход, который исполняется, когда заданное нами условие выполняется (имеет значение true - истина - определенный бит в регистре flags). Лучше всего показать на примере: Мы сравниваем ax с bx (cmp ax,bx). Теперь условный переход может быть, если ax < bx, или ax = bx, или ax >= bx и так долее. Эти переходы (op1 – первый операнд cmp, op2 - второй):
  • je - переход, если op1 = op2 (op1 equal to op2)
  • ja - переход, если op1 > op2 (op1 above op2)
  • jb - переход, если op1 < op2 (op1 below op2)
  • jae - переход, если op1 >= op2 (op1 above or equal to op2)
  • jbe - переход, если op1 <= op2 (op1 below or equal to op2)
Пример кода: (но не пытайтесь компилировать его, это не пример исполняемого COM файла, лишь кусок кода)
Assembler
1
2
3
4
    cmp ax, 10
    jbe AX_lesser_than_10
    mov ax, 10
AX_lesser_than_10:
Этот кусок проверит является ли значение ax меньше или равно 10, и если нет (если больше 10) поместит в ax число 10. Соответствующий код C:
C
1
if (ax>10) ax=10;
или более похожий на ассемблерную версию:
C
1
2
3
if (ax <= 10) goto AX_lesser_than_10
  ax=10;
AX_lesser_than_10:
Другой пример: взять большее значение из {ax,bx} и поместить его в ax:
Assembler
1
2
3
4
    cmp ax, bx
    jae AX_already_contains_bigger_value
    mov ax, bx
AX_already_contains_bigger_value:
Итак, сравниваем значение ax с bx, если оно больше или равно, значит в ax и так большее значение, нам не нужно ничего менять. Если ax меньше, чем bx, то необходимо скопировать значение из bx (то есть большее значение) в ax.
Более сложная версия: записать максимум из {ax,bx} в cx:
Assembler
1
2
3
4
5
6
7
    cmp ax, bx
    ja  AX_bigger
    mov cx, bx
    jmp done
AX_bigger:
    mov cx, ax
done:
1
Ушел с форума
Автор FAQ
16276 / 7601 / 1064
Регистрация: 11.11.2010
Сообщений: 13,616
11.09.2014, 13:12  [ТС] 7
Таким образом, мы сравниваем ax с bx, затем, если ax меньше чем bx переход не произойдет и мы продолжим mov cx, bx записав большее значение в cx, как и нужно, и потом перейдем к метке done пропустив инструкцию, исполняемую в случае, если ax больше. Если ax больше чем bx, произойдет jmp AX_bigger, так что следующая инструкция будет mov cx, ax помещающая большее значение (из ax) в cx. Вы видите, код разделился на 2 ветви: одна для ax>bx, другая для ax<=bx. В итоге обе ветви ведут к инструкции после done:, и в этом месте cx всегда содержит максимальное значение. К стати, здесь могла бы быть инструкция jae вместо ja, так как для случая ax=bx обе ветви имеют одинаковый эффект.
Assembler
1
инструкции je, ja, jb, jae, jbe
Но что можно сделать, если нужен переход, когда операнды НЕ равны? Можно было бы сделать так:

Assembler
1
2
3
4
5
6
    cmp ax, bx
    je  Same
    jmp NotSame
Same:
;.....
NotSame:
Но в этом нет необходимости, потому что имеются инструкции для перехода, когда условие не выполняется. Это: jne, jna, jnb, jnae, jnbe. jne выполняет переход, когда операнды не равны, jna – когда первый не больше второго и так далее, так что:
Assembler
1
2
3
4
    cmp ax, bx
    jne NotSame:
;.....
NotSame:
и часть ;..... выполнится только когда значение ax не равно bx.
ПРИМЕЧАНИЕ: Различные мнемоники инструкций условного ветвления могут обозначать одну и ту же инструкцию. Так, jna - это тоже, что и jbe; jnb эквивалентна jae; jb - jnae; ja - jnbe.

Assembler
1
инструкции jne, jna, jnb, jnae, jnbe
ПРИМЕЧАНИЕ: многие инструкции изменяют регистр флагов, не только cmp. Так что условные переходы желательно делать сразу после cmp, никаких инструкций между ними.
2
11.09.2014, 13:12
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
11.09.2014, 13:12
Помогаю со студенческими работами здесь

irrlicht. tutorial 1
Пробую работать с движком irrlicht. Пробую выполнить первый урок. В студии создаю консольный...

Подскажите tutorial по Билдеру
Где можно найти гайд по работе формами ? Все , что нашел - только обзор интерфеса билдера....

Tutorial на русском языке
Добрый день, форумчане. Ищу тутор по использованию Node + angular + mongo для создания хотя бы...

ASP.NET 1.x QuickStart Tutorial
Столкнулся вот с чем. С помощью &quot;Microsoft Visual Web Developer 2005 Express Edition&quot; делаю пустой...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
7
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru