Двійкова система числення.
У попередньому розділі ми розглянули з вами шестнадцатеричную систему числення і просту програму виведення рядка на екран.
Якщо ви повністю розберетеся в усьому в цьому розділі, то вважайте, що базу Ассемблера ви вивчили. З наступного глави будемо вивчати мову набагато інтенсивніше.
Для того щоб краще зрозуміти сегментацію пам'яті, нам потрібно скористатися отладчиком. Я працював з CodeView (CV.EXE) і AFD Pro (AFD.EXE).
Припустимо, ви написали програму на асемблері і назвали її PROG03.ASM. Сассембліровав, ви отримали файл. Тоді, щоб запустити її під отладчиком CodeView / AFD, необхідно набрати в командному рядку MS-DOS наступне:
Отже, вдихніть глибше і - вперед!
Розглянемо, як в пам'яті комп'ютера зберігаються дані.
Взагалі, як комп'ютер може зберігати, наприклад, слово "диск"? Головний принцип - намагнічування і розмагнічування однієї доріжки (назвемо її так). Одна мікросхема пам'яті - це, грубо кажучи, величезна кількість доріжок (приблизно як на магнітофонного касеті). Зараз спробуємо розібратися.
нуль буде позначатися як 0000 (чотири нулі),
Два - 0010 (тобто праву одиницю замінюємо на 0, а другу встановлюємо в 1).
Вловили принцип? "0" і "1" - це т.зв. біти. Один біт, як ви вже помітили, може бути нулем або одиницею, тобто розмагнічена або намагнічена та чи інша доріжка ( "0" і "1" - це умовне позначення). Придивившись, можна виявити, що кожен наступний встановлений біт (починаючи справа) збільшує число в два рази: 0001 в нашому прикладі - одиниця; 0010 - два; 0100 - чотири; 1000 - вісім і т.д. Це і є т.зв. двоичная форма представлення даних.
Т.ч. щоб позначити числа від 0 до 9, нам потрібно чотири біта (хоч вони і не до кінця використані. Можна було б продовжити: десять - 1010, одинадцять - 1011 ..., п'ятнадцять - 1111).
Комп'ютер зберігає дані в пам'яті саме так. Для позначення якогось символу (цифри, букви, коми, крапки ...) в комп'ютері використовується певна кількість біт. Комп'ютер "розпізнає" 256 (від 0 до 255) різних символів по їх коду. Цього достатньо, щоб вмістити всі цифри (0 - 9), літери латинського алфавіту (a - z, A - Z), російського (а - я, А - Я) і ін. Для подання символу з максимально можливим кодом (255) потрібно 8 біт. Ці 8 біт називаються байтом. Т.ч. один будь-який символ - це завжди 1 байт (див. рис. 1).
Мал. 1. Один байт з кодом символу "Z"
(Символи "Н" і "Р" позначають Н амагнічено або Р азмагнічено відповідно)
Т.ч. слово "диск" буде займати 4 байти або 4 * 8 = 32 біта. Як ви вже зрозуміли, комп'ютер зберігає в пам'яті не самі букви (символи) цього слова, а послідовність "одиничок" та "налякав".
У Ассемблері після двійкового числа завжди повинна стояти буква "b". Це потрібно для того, щоб при асемблюванні програми Асемблер зміг відрізняти десяткові, шістнадцяткові і виконавчі числа. Наприклад: 10 - це "десять", 10h - це "шістнадцять" а 10b - це "два" в десятковій системі.
Т.ч. в регістри можна завантажувати виконавчі, десяткові і шістнадцяткові числа.
В результаті в регістрах AX, BH і CL буде знаходитися одне і теж число, тільки завантажуємо ми його, використовуючи різні системи числення. Комп'ютер же буде зберігати його в двійковому форматі (як в регістрі BH).
Отже, підіб'ємо підсумок. У комп'ютері вся інформація зберігається в двійковому форматі (двійковій системі) приблизно в такому вигляді: 10101110 10010010 01111010 11100101 (природно, без пробілів. Для зручності я розділив байти). Вісім біт - це один байт. Один символ займає один байт, тобто вісім біт. По-моєму, нічого складного. Дуже важливо усвідомити цю тему, так як ми будемо постійно користуватися двійковій системою, і вам необхідно знати її на "відмінно". В принципі, навіть якщо щось не зовсім зрозуміло, то - не впадайте у відчай! Згодом все стане зрозуміло.
Як перевести двійкове число в десяткове:
Треба скласти двійки в ступенях, відповідних позиціях, де в довічним стоять одиниці.
Візьмемо число 20. У двійковій системі воно має такий вигляд: 10100b
Отже (почнемо зліва направо, рахуючи від 4 до 0; число в нульовому ступені завжди дорівнює одиниці (згадуємо шкільну програму з математики)):
10100b = 1 * 16 + 0 * 8 + 1 * 4 + 0 * 2 + 0 * 1 = 20
Як перевести десяткове число в двійкове:
Можна ділити його на два, записуючи залишок справа наліво:
20/2 = 10, залишок 0
В результаті отримуємо: 10100b = 20
Як перевести шістнадцяткове число в десяткове:
У шістнадцятковій системі номер позиції цифри в числі відповідає ступеню, в яку треба звести число 16:
8Ah = 8 * 16 + 10 (0Ah) = 138
На даний момент є безліч калькуляторів, які можуть вважати і переводити числа в різних системах числення. Наприклад, калькулятор Windows, який повинен бути в інженерному вигляді. Дуже зручний калькулятор і в DOS Navigator'е. Якщо у вас є він, то відпадає необхідність в ручному перекладі однієї системи в іншу, що, природно, спростить роботу. Однак, знати цей принцип вкрай важливо!
Сегментація пам'яті в реальному режимі.
Візьмемо таку пропозицію: "Вивчаємо сегменти пам'яті". Тепер давайте порахуємо, на якому місці стоїть буква "и" в слові "сегменти" від початку пропозиції, включаючи прогалини ... На шістнадцятому. Підкреслю, що ми вважали від початку пропозиції.
Тепер трохи ускладнити завдання і розіб'ємо пропозицію наступним чином (символом "_" позначений пробіл):
Регістр CS служить для зберігання сегмента коду програми (Code Segment - сегмент коду);
Регістр DS - для зберігання сегмента даних (Data Segment - сегмент даних);
Регістр SS - для зберігання сегмента стека (Stack Segment - сегмент стека);
Давайте спробуємо завантажити в пару регістрів ES: DI сегмент і зсув букви "м" в слові "пам'яті" з Прикладу № 1 (див. Вище). Ось як це запишеться на Асемблері:
Тепер в регістрі ES знаходиться сегмент з номером 20, а в регістрі DI - зміщення до букви (символу) "м" в слові "пам'яті". Перевірте будь ласка…
Тут варто відзначити, що завантаження числа (тобто якогось сегмента) безпосередньо в сегментний регістр заборонена. Тому ми в рядку (1) завантажили сегмент в AX, а в рядку (2) завантажили в регістр ES число 20, яке знаходилося в AX:
Коли ми завантажуємо програму в пам'ять, вона автоматично розташовується в першому вільному сегменті. У файлах типу * .com все сегментні регістри автоматично не започатковано для цього сегмента (встановлюються значення рівні того сегменту, в який завантажена програма). Це можна перевірити за допомогою відладчика. Якщо, наприклад, ми завантажуємо програму типу * .com в пам'ять, і комп'ютер знаходить перший вільний сегмент з номером 5674h, то сегментні регістри матимуть наступні значення:
Інакше кажучи: CS = DS = SS = ES = 5674h
Код програми типу * .com повинен починатися зі зміщення 100h. Для цього ми, власне, і ставили в наших минулих прикладах програм оператор ORG 100h, вказуючи Асемблеру при асемблюванні використовувати зміщення 100h від початку сегмента, в який завантажена наша програма (пізніше ми розглянемо, чому так). Сегментні ж регістри, як я вже говорив, автоматично приймають значення того сегмента, в який завантажилася наша програма.
(01) CSEG segment
(05) mov dx, offset My_name
(08) My_name db 'Dima $'
Отже, рядки (01) і (09) описують сегмент:
CSEG (даємо ім'я сегменту) segment (оператор Ассемблера, який вказує, що ім'я CSEG - це назва сегмента);
CSEG ends (END Segment - кінець сегмента) вказує Асемблеру на кінець сегмента.
1234h: 0100h (тобто CS = 1234h, а IP = 0100h) (подивіться в отладчике на регістри CS і IP).
Перейдемо до наступної команді (в відладчик CodeView натисніть клавішу F8, в AFD - F1, в іншому - подивіться, яка клавіша потрібна; буде написано щось на кшталт "F8-Step" або "F7-Trace"). Тепер ви бачите, що змінилися такі регістри:
AX = 0900h (точніше, AH = 09h, а AL = 0, тому що ми завантажили командою mov ah, 9 число 9 в регістр AH, при цьому не чіпаючи AL. Якби AL дорівнював, скажімо, 15h, то після виконання даної команди AX б дорівнював 0915h)
Зверніть увагу, що в Асемблері ми пишемо:
mov dx, offset My_name
а в отладчике бачимо наступне:
mov dx, 109; (109 - шістнадцяткове число, але CodeView і багато інших отладчики символ 'h' не ставлять. Це треба мати на увазі).
(09) My_name db 'Dima $'
Просто продублюємо команду int 20h (хоча, як ви вже знаєте, до рядка (08) програма не дійде).
У вікні "Memory" ( "Пам'ять") відладчика CodeView (у AFD щось подібне) ви повинні побачити приблизно наступне:
Позиція №1 (-1234) - сегмент, в який завантажилася наша програма (може бути будь-яким).
Позиція №2 (0000) - зміщення в даному сегменті (сегмент і зсув відокремлюються двокрапкою (:)).
Позиція №4 (= .a.) - код в ASCII (нижче розглянемо), відповідний шістнадцятковим числах з правого боку.
Як я вже говорив раніше, для використання переривань в програмах, в AH заноситься номер функції. Номери функцій бажано запам'ятовувати (хоча б частоїспользуємиє) з тим, щоб постійно не шукати в довідниках яка функція що робить.
Наше перше переривання
Взагалі, будь-який рядок, що складається з ASCII символів, називається ASCII-рядок. ASCII символи - це символи від 0 до 255 в DOS, куди входять літери російського і латинського алфавітів, цифри, знаки пунктуації та ін. (Повний список ASCII-символи різних кодувань дивіться в Додатку № 04).
Зобразимо це в таблиці (так завжди будемо робити):
Функція 09h переривання 21h - висновок рядка символів на екран в поточну позицію курсору:
В поле "Вхід" ми вказуємо, в які регістри що загрожують перед викликом переривання, а в поле "Вихід" - що повертає функція. Порівняйте цю таблицю з Прикладом № 3.
Ось ми і розглянули сегментацію пам'яті. Якщо я щось упустив, то це розглянемо в наступних розділах. Маю велику надію на те, що ви розібралися в даній темі. По крайней мере, вловили принцип сегментації пам'яті.
Програма для практики
Тепер цікава програма (/003/PROG03.ASM) для практики, яка виводить у верхній лівий кут екрану веселу пику на синьому тлі:
(01) CSEG segment
(04) mov ax, 0B800h
Багато операторів ви вже знаєте. Тому я буду пояснювати тільки нові.
У рядках (04) і (05) завантажуємо в сегментний регістр ES число 0B800h, яке відповідає сегменту дисплея в текстовому режимі (запам'ятайте його!). У рядку (06) завантажуємо в регістр DI нуль. Це буде зсув щодо сегмента 0B800h. У рядках (08) і (09) в регістр AH заноситься атрибут символу (31 - яскраво-білий символ на синьому тлі) і в AL - ASCII-код символу (01 - це пика).
Можете поекспериментувати з даним прикладом. Тільки не міняйте поки рядки (04) і (05). Сегментний регістр повинен бути ES (можна, звичайно, і DS, але тоді треба бути обережним). Більш детально даний метод розглянемо пізніше. Зараз нам з нього потрібно зрозуміти принцип сегментації на практиці.
Шановний читачу! Ось ви і ознайомилися з першою частиною книги "Асемблер? Це просто! Вчимося програмувати".
Метою цієї частини - було дати вам основи програмування на найшвидшому, компактному і унікальною мовою програмування - Ассемблері. Якщо ви засвоїли як мінімум 75% матеріалу з даної частини, то, можна сказати, я домігся того, чого хотів. Якщо ж ви відчуваєте, що багато чого незрозуміло, то спробуйте прочитати все ще раз з самого початку, скористайтеся отладчиком, напишіть мені листа, в якому викласти питання, надайте максимум можливої інформації (якою програмою-ассемблером користуєтеся, що ви робили для вирішення виниклої проблеми і пр.). Відповідь гарантую вам в самий найближчий час. Проте, згодом ми будемо ще не раз повертатися до матеріалу, який викладено в першій частині. Якщо ви щось не зрозуміли зараз, то, сподіваюся, зрозумієте з часом. Найцікавіше і захоплююче - попереду!
У разі, якщо питань з пройденого матеріалу у вас не виникло, то можете сміливо приступати до вивчення другої частини книги - "Від знайомства - до дружби".
Для передплатників розсилки
"Асемблер? Це просто! Вчимося програмувати".
Наступний файл, який необхідно вивчати - 004.htm. потім - 005.htm і т.д.
Захоплюючого вам читання!