|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Опис даних
Практично будь-яка програма містить
в собі перелік даних, з
якими вона працює. Це можуть бути
символьні рядки, призначені для виводу на
екран; числа, що визначають хід виконання
програми або що беруть участь
в обчисленнях; адреси підпрограм,
обробників переривань або просто
тих або інших полів програми; спеціальні
коди, наприклад, коди кольору символів,
що виводяться на екран, і так далі
Окрім даних, визначуваних в тексті програми,
в програму часто входять
зарезервовані поля, призначені для заповнення
по ходу виконання програми,
наприклад, результатами обчислень або шляхом
читання з файлу. Всі ці дані
і зарезервовані поля мають
бути визначені у складі сегменту
даних програми (в принципі вони можуть
бути визначені, і часто визначаються, не
в сегменті даних, а в сегменті команд,
але тут ми не стосуватимемося цього питання).
Окрім перерахованих, є і інші
директиви, наприклад df (define fanvord, визначити
поле з 6 байт), dq (define quadword, визначити
четверне слово) або dt (define tcraword, визначити
10-байтову змінну), але вони використовуються
значно рідше.
Значення числових даних можна записувати в різних системах числення; частіше за інших використовуються десяткова і 16-річная запис:
Необхідно відзначити неточність приведених
вище коментарів. У пам'яті комп'ютера
можуть зберігатися тільки двійкові коди.
Якщо ми говоримо, що в якомусь осередку записано
десяткове число 128, ми маємо на увазі
не фізичний вміст осередку, а лише
форму представлення цього числа в початковому
тексті програми. У слові з ім'ям size
фактично буде записаний двійковий код 0000000100000000,
що є двійковим еквівалентом
десяткового числа 256. У другому випадку в
байті з ім'ям setbit? буде записаний двійковий
еквівалент шістнадцяткового числа 80h,
який складає 10000000 (тобто байт зі
встановленим бітом 7, звідки і отримав ім'я
цей осередок).
Привласнення даним символічних імен дозволяє звертатися до них в програмних пропозиціях, не піклуючись про фактичні адреси цих даних. Наприклад, команда mov Ax,size занесе в регістр АХ вміст осередку size (число 256), незалежно від того, в якому місці сегменту даних цей осередок визначений, і в яке місце фізичної пам'яті вона потрапила. Проте програміст, що використовує мову асемблера, повинен мати виразне уявлення про те, яким чином призначаються адреси осередкам програми, і уміти працювати не тільки з символічними позначеннями, але і із значеннями адрес. Для обговорення цього питання розглянемо приклад сегменту даних, в якому визначаються дані різних типів. У лівій колонці вкажемо зсуви даних (у шістнадцятиричній формі), що обчислюються відносно початки сегменту.
Сегмент даних починається з даного на ім'я counter, яке
описане, як слово (2 байт) і містить число 10000. Очевидно, що його зсув
дорівнює 0. Оскільки це дане займає 2 байт, наступне за ним дане pages отримало
зсув 2. Дане pages описує рядок тексту завдовжки 10 символів і займає в пам'яті
стільки ж байтів, тому наступне дане numbers отримало відносну адресу 2 + 10 =
12 = Ch. У полі numbers
записано 5 байтових чисел, тому
останнє дане сегменту з ім'ям page_addr
розміщується за адресою Ch + 5 = 11h. page_addr dw pages трактується асемблером, як page_addr dw 2 і приводить до запису в слово з
відносною адресою 11h числа 2 (зсуви
рядка pages).
Тут ми "уручну" визначили
зсув символу, що цікавить нас,
в рядку, знаючи, що всі дані
розміщуються асемблером один за одним в порядку
їх оголошення в програмі. При цьому, якого
б значення не набуло ім'я pages, вираз pages
+ 9 завжди відповідатиме байту з
номером сторінки.
Який сенс мало об'єднання ряду чисел в масив numbers? Та ніякого, якщо до цих чисел ми все одно звертаємося окремо. Зручніше було оголосити цей масив таким чином:
В цьому випадку для звернення до останнього елементу не треба обчислювати його адресу, а можна скористатися ім'ям nmb4. Якщо, з іншого боку, ми хочемо працювати з числами, як з масивом, використовуючи індекси окремих елементів (про що мова йтиме пізніше), те привласнення масиву загального імені представляється природним. Отримання останнього елементу масиву по його індексу виконується за допомогою такої послідовності команд:
Іноді бажано звертатися до елементів масиву (зазвичай невеликого розміру) то за допомогою індексів, то по їх іменах. Для цього треба до опису масиву, як послідовності окремих даних, додати додатковий символічний опис адреси початку масиву за допомогою директиви асемблера label (влучна):
Влучна numbers має бути оголошена в даному випадку з описувачем byte, оскільки дані, наступні за цією міткою, описані як байти і ми плануємо працювати з ними саме як з байтами. Якщо нам потрібно мати масив слів, то окремі елементи масиву слід оголосити за допомогою директиви dw, а мітці numbers додати описувач word:
У чому полягає
відмінність двох останніх описів даних? Відмінність
є, і вельми істотне. Хоча в обох
випадках в пам'ять записується натуральний ряд чисел від
0 до 4, проте в першому варіанті під
кожне число в пам'яті відводиться один байт, а
в другому - слово. Якщо ми надалі
змінюватимемо значення елементів нашого масиву, то
в першому варіанті кожному числу' можна
буде задавати значення від 0 255, а в другому - від 0
до 65535.
Так робити не можна. Транслятор повідомить про грубу помилку - невідповідність типів, і не створюватиме об'єктний файл. Проте досить часто виникає реальна потреба в операціях такого роду. Для таких випадків передбачений спеціальний атрибутивний оператор byte ptr (byte pointer, байтовий покажчик), за допомогою якого можна на час виконання однієї Команди змінити розмір операнда:
Ці команди транслятор розглядає, як
правильні.
Тут обидва байти з байтової змінної okey переносяться
в регістр АХ. При цьому перший по порядку
байт, тобто байт з меншою адресою, що
містить букву "О" (можна вважати,
що він є молодшим в слові
Останні дві команди повністю
еквівалентні.
Такий запис достатньо наочний, і її
легко модифікувати, якщо ми вирішимо вивести
символ в якусь іншу область екрану.
В даному прикладі константа mes_len набуває значення довжини рядка mes (в даному випадку 5 байт), яка обчислюється як різниця значення лічильника поточної адреси після визначення рядка і її початкової адреси mes. Такий спосіб зручний тим, що при зміні вмісту рядка досить перетранслювати програму, і та ж константа mes_len автоматично набуде нового значення.
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|