Категория > Новости > Погружение в ассемблер. Как работают переменные, режимы адресации, инструкции условного перехода - «Новости»

Погружение в ассемблер. Как работают переменные, режимы адресации, инструкции условного перехода - «Новости»


11-08-2020, 12:55. Автор: Smith
На ассемблере ты можешь хранить переменные двумя способами: в регистрах и в памяти. С регистрами все понятно. А вот с памятью у тебя могут возникнуть проблемы. Тут есть подводные камни. Читай дальше, и ты узнаешь два способа размещения переменных, которыми пользоваться нельзя, и три — которыми можно. Также ты узнаешь, какие бывают режимы адресации и как это знание поможет тебе кодить на ассемблере более эффективно.

После первых двух уроков ты умеешь пользоваться 16-битными регистрами процессора и их 8-битными половинками. Это хорошее начало. Но! Программируя на ассемблере, очень часто сталкиваешься с тем, что нужно намного больше переменных, чем может поместиться в регистры процессора. Часть переменных приходится хранить в памяти. Сейчас расскажу, как это делать. На примере программы, которая ищет простые числа.


Предыдущие статьи



  • Погружение в assembler

  • Делаем первые шаги в освоении асма

  • Осваиваем арифметические инструкции


Где данные хранить можно, а где нельзя



Перед тем как размещать в памяти переменные, разберись, куда их можно всовывать, а куда нельзя. Потому что здесь ассемблер тебя никак не контролирует. Ты волен размещать переменные всюду, где только захочешь. Ассемблер все безропотно скомпилирует, а процессор все прилежно выполнит. Вот только ответственности за последствия они не несут. Вся ответственность целиком лежит на тебе. Но ты не пугайся! Просто постарайся запомнить раз и навсегда два способа размещения переменных, которыми пользоваться нельзя, и три — которыми можно. Сначала — как нельзя.



  1. Из двух предыдущих уроков ты уже знаешь, что первые 0x100 байтов любой программы, скомпилированной РІ файл СЃ расширением .com, зарезервированы операционной системой. Всовывать сюда свои переменные точно не стоит.

  2. Процессор 8088 не отличает данные от кода. Если ты напишешь на ассемблере вот такой код, процессор радостно выполнит не только строчки с инструкциями, но и строчки с данными — ничуть не переживая о том, что от выполнения строчек с данными получается в лучшем случае абракадабра, а в худшем программа рушится или застревает в бесконечном цикле.



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



Есть по крайней мере три участка, куда процессор никогда не заглядывает. Вот там и храни свои переменные:



  • после инструкции 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. В Википедии есть наглядная анимированная гифка. Посмотри ее, и сразу поймешь, как работает решето Эратосфена.



Погружение в ассемблер. Как работают переменные, режимы адресации, инструкции условного перехода - «Новости»

Что здесь происходит?



  1. Начинаем с двойки.

  2. Смотрим: очередное число a помечено как составное? Да — идем РЅР° шаг 5.

  3. Если РЅРµ помечено (РЅРµ вычеркнуто), значит, a — простое.

  4. Пробегаем по всему списку и вычеркиваем все числа, которые делятся на a.

  5. Р?нкрементируем текущее число.

  6. Повторяем шаги 2–5, пока не достигли n.

Каким образом будем бегать по списку и вычеркивать оттуда составные числа? Сначала нам этот список надо создать! Причем в регистры его точно втиснуть не получится. Нам потребуется битовый массив размером n. РџРѕ биту РЅР° каждое число РѕС‚ 2 до n (или вполовину меньше, если РјС‹ оптимизируем алгоритм так, чтобы РѕРЅ РЅРµ видел четные числа; это ты можешь сделать РІ качестве домашнего задания). Соответственно, чем больше n, до которого ты хочешь найти простое число, тем вместительней должен быть массив.



Все понятно? Давай закодим!



Перейти обратно к новости