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

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

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

 

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

  • Погружение в assembler
  • Делаем первые шаги в освоении асма
  • Осваиваем арифметические инструкции

 

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

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

  • Из двух предыдущих уроков ты уже знаешь, что первые 0x100 байтов любой программы, скомпилированной в файл с расширением .com, зарезервированы операционной системой. Всовывать сюда свои переменные точно не стоит.
  • Процессор 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. В Википедии есть наглядная анимированная гифка. Посмотри ее, и сразу поймешь, как работает решето Эратосфена.

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

  • Начинаем с двойки.
  • Смотрим: очередное число a помечено как составное? Да — идем на шаг 5.
  • Если не помечено (не вычеркнуто), значит, a — простое.
  • Пробегаем по всему списку и вычеркиваем все числа, которые делятся на a.
  • Инкрементируем текущее число.
  • Повторяем шаги 2–5, пока не достигли n.
  • Каким образом будем бегать по списку и вычеркивать оттуда составные числа? Сначала нам этот список надо создать! Причем в регистры его точно втиснуть не получится. Нам потребуется битовый массив размером n. По биту на каждое число от 2 до n (или вполовину меньше, если мы оптимизируем алгоритм так, чтобы он не видел четные числа; это ты можешь сделать в качестве домашнего задания). Соответственно, чем больше n, до которого ты хочешь найти простое число, тем вместительней должен быть массив.

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

    Источник: xakep.ru

    Ответить

    Ваш адрес email не будет опубликован. Обязательные поля помечены *