Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
На ассемблере ты можешь хранить переменные двумя способами: в регистрах и в памяти. С регистрами все понятно. А вот с памятью у тебя могут возникнуть проблемы. Тут есть подводные камни. Читай дальше, и ты узнаешь два способа размещения переменных, которыми пользоваться нельзя, и три — которыми можно. Также ты узнаешь, какие бывают режимы адресации и как это знание поможет тебе кодить на ассемблере более эффективно.
После первых двух уроков ты умеешь пользоваться 16-битными регистрами процессора и их 8-битными половинками. Это хорошее начало. Но! Программируя на ассемблере, очень часто сталкиваешься с тем, что нужно намного больше переменных, чем может поместиться в регистры процессора. Часть переменных приходится хранить в памяти. Сейчас расскажу, как это делать. На примере программы, которая ищет простые числа.
Перед тем как размещать в памяти переменные, разберись, куда их можно всовывать, а куда нельзя. Потому что здесь ассемблер тебя никак не контролирует. Ты волен размещать переменные всюду, где только захочешь. Ассемблер все безропотно скомпилирует, а процессор все прилежно выполнит. Вот только ответственности за последствия они не несут. Вся ответственность целиком лежит на тебе. Но ты не пугайся! Просто постарайся запомнить раз и навсегда два способа размещения переменных, которыми пользоваться нельзя, и три — которыми можно. Сначала — как нельзя.
0x100
байтов любой программы, скомпилированной в файл с расширением .com
, зарезервированы операционной системой. Всовывать сюда свои переменные точно не стоит.Никогда так не делай! Когда резервируешь какую-то область памяти под переменную, обязательно проследи, чтобы эта область памяти была за пределами потока выполнения. Где же такую область найти? Ведь ассемблерные инструкции, по которым процессор идет, теоретически могут размещаться в любой ячейке памяти.
Есть по крайней мере три участка, куда процессор никогда не заглядывает. Вот там и храни свои переменные:
int 0x20
, которую мы ставим в конце программы, чтобы вернуться в командную строку ОС;jmp
;ret
.
В начале статьи об арифметических функциях мы написали библиотеку library.asm
с двумя функциями: display_letter
(выводит букву на экран) и read_keyboard
(считывает символ с клавиатуры). Сейчас для вывода простых чисел на экран нам нужна более продвинутая функция вывода, которая выводит не одну букву или цифру, а полноценное число. Давай напишем такую функцию (добавь ее в конец файла library.asm
).
Как она работает? Берет из AX
число, которое надо вывести на экран. Рекурсивно делит его на 10. После каждого деления сохраняет остаток на стеке. Доделившись до нуля, начинает выходить из рекурсии. Перед каждым выходом снимает со стека очередной остаток и выводит его на экран. Уловил мысль?
На всякий случай, если ты с ходу не понял, как работает display_number
, вот тебе три примера.
Допустим, AX = 4
. Тогда после деления на 10 в AX
будет 0, и поэтому display_number
не зайдет в рекурсию. Просто выведет остаток, то есть четверку, и всё.
Если AX = 15
, то после деления на 10 в AX
будет единица. И поэтому подпрограмма залезет в рекурсию. Покажет там единицу, затем выйдет из внутреннего вызова в основной и там напечатает цифру 5.
Если ты так до конца и не понял, то сделай вот что. Помести в AX
число побольше, скажем 4527
, и поработай в роли процессора: пройди мысленно по всем строкам программы. При этом отмечай в блокноте — в обычном бумажном блокноте, не на компьютере — каждый свой шаг. Когда в очередной раз заходишь рекурсивно в display_number
, отступай в блокноте на один символ вправо от начала строки. А когда выходишь из рекурсии (инструкция ret
), отступай на один символ влево.
И еще: имей в виду, что после того, как display_number
выполнится, в AX
уже не будет того значения, которое ты туда поместил перед тем, как вызвать подпрограмму.
Два предварительных шага сделаны: ты уяснил, где переменные размещать можно, а где нельзя, и ты написал функцию печати десятичного числа. Теперь давай пощупаем всю эту теорию руками. Напишем с тобой программу, которая ищет простые числа.
Напомню, простые числа — это такие, которые делятся только на единицу и на себя. Если у них есть другие делители, то такие числа называются составными. Для поиска простых чисел существует целая куча алгоритмов. Мы воспользуемся одним из них — решетом Эратосфена. В чем суть алгоритма? Он постепенно отфильтровывает все числа за исключением простых. Начиная с числа 2
и заканчивая n
. Число n
задаешь ты. Как только алгоритм натыкается на очередное простое число a
, он пробегает по всему списку до конца (до n
) и вычеркивает из него все числа, которые делятся на a
. В Википедии есть наглядная анимированная гифка. Посмотри ее, и сразу поймешь, как работает решето Эратосфена.
Что здесь происходит?
a
помечено как составное? Да — идем на шаг 5.a
— простое.a
.n
.Каким образом будем бегать по списку и вычеркивать оттуда составные числа? Сначала нам этот список надо создать! Причем в регистры его точно втиснуть не получится. Нам потребуется битовый массив размером n
. По биту на каждое число от 2
до n
(или вполовину меньше, если мы оптимизируем алгоритм так, чтобы он не видел четные числа; это ты можешь сделать в качестве домашнего задания). Соответственно, чем больше n
, до которого ты хочешь найти простое число, тем вместительней должен быть массив.
Все понятно? Давай закодим!
Источник: xakep.ru