Меню English Ukrainian російська Головна

Безкоштовна технічна бібліотека для любителів та професіоналів Безкоштовна технічна бібліотека


Інформатика та інформаційні технології. Конспект лекцій: коротко, найголовніше

Конспекти лекцій, шпаргалки

Довідник / Конспекти лекцій, шпаргалки

Коментарі до статті Коментарі до статті

Зміст

  1. Введення в інформатику (Інформатика. Інформація. Подання та обробка інформації. Системи числення. Подання чисел в ЕОМ. Формалізоване поняття алгоритму)
  2. Мова Pascal (Введення в мову Pascal. Стандартні процедури та функції. Оператори мови Pascal)
  3. Процедури та функції (Поняття допоміжного алгоритму. Процедури в Pascal. Функції в Pascal. Випереджальні описи та підключення підпрограм. Директива)
  4. Підпрограми (Параметри підпрограм. Типи параметрів підпрограм. Строковий тип у Pascal. Процедури та функції для змінних рядкового типу. Записи. Множини)
  5. Файли (Файли. Операції з файлами. Модулі. Види модулів)
  6. Динамічна пам'ять (Посилальний тип даних. Динамічна пам'ять. Динамічні змінні. Робота з динамічною пам'яттю. Нетипізовані покажчики)
  7. Абстрактні структури даних (Абстрактні структури даних. Стеки. Черги)
  8. Деревоподібні структури даних (Деревоподібні структури даних. Операції над деревами. Приклади реалізації операцій)
  9. Графи (Поняття графа. Способи подання графа. Подання графа списком інцидентності. Алгоритм обходу графа в глибину. Подання графа списком списків. Алгоритм обходу графа завширшки)
  10. Об'єктний тип даних (Об'єктний тип у Pascal. Поняття об'єкта, його опис та використання. Спадкування. Створення екземплярів об'єктів. Компоненти та сфера дії)
  11. Методи (Методи. Конструктори та деструктори. Деструктори. Віртуальні методи. Поля даних об'єкта та формальні параметри методу)
  12. Сумісність типів об'єктів (Інкапсуляція. Об'єкти, що розширюються. Сумісність типів об'єктів)
  13. Асемблер (Про асемблеру. Програмна модель мікропроцесора. Регістри користувача. Регістри загального призначення. Сегментні регістри. Регістри стану та управління)
  14. Регістри (Системні регістри мікропроцесора. Регістри управління. Регістри системних адрес. Регістри налагодження)
  15. Програми на асемблері (Структура програми на асемблері. Синтаксис асемблера. Оператори порівняння. Оператори та їх пріоритет. Спрощені директиви визначення сегмента. Ідентифікатори, що створюються директивою MODEL. Моделі пам'яті. Модифікатори моделі пам'яті)
  16. Структури команд на асемблері (Структура машинної команди. Способи завдання операндів команди. Способи адресації)
  17. Команди (Команда пересилання даних. Арифметичні команди)
  18. Команди передачі управління (Логічні команди. Таблиця істинності для логічного заперечення. Таблиця істинності для логічного включає АБО. Таблиця істинності для логічного І. Таблиця істинності для логічного виключає АБО. Значення абревіатур у назві команди jcc. Перелік команд умовного переходу для команди. Команди у команди.

ЛЕКЦІЯ № 1. Введення в інформатику

1. Інформатика. Інформація. Подання та обробка інформації

Інформатика займається формалізованим уявленням об'єктів та структур їх взаємозв'язків у різних галузях науки, техніки, виробництва. Для моделювання об'єктів та явищ використовуються різні формальні засоби, наприклад, логічні формули, структури даних, мови програмування та ін.

В інформатиці таке фундаментальне поняття, як інформація, має різні значення:

1) формальне подання зовнішніх форм інформації;

2) абстрактне значення інформації, її внутрішній зміст, семантика;

3) відношення інформації до реального світу.

Але, зазвичай, під інформацією розуміють її абстрактне значення - семантику. Інтерпретуючи уявлення інформації, ми матимемо її сенс, семантику. Тому якщо ми хочемо обмінюватися інформацією, нам необхідні узгоджені уявлення, щоб не порушувалася правильність інтерпретації. Для цього інтерпретацію подання інформації ототожнюють із деякими математичними структурами. І тут обробка інформації може бути виконана строгими математичними методами.

Одне з математичних описів інформації - це її у вигляді функції у =f(x, t), де t - час, x - точка деякого поля, у якій вимірюється значення у. Залежно від параметрів функції хі(інформацію можна класифікувати).

Якщо параметри - скалярні величини, що набирають безперервний ряд значень, то отримана таким чином інформація називається безперервною (або аналоговою). Якщо параметрам надати певний крок змін, то інформація називається дискретною. Дискретна інформація вважається універсальною, тому що для кожних конкретних параметрів можна отримати значення функції із заданим ступенем точності.

Дискретну інформацію зазвичай ототожнюють із цифровою інформацією, яка є окремим випадком символьної інформації алфавітного подання. Алфавіт – кінцевий набір символів будь-якої природи. Дуже часто в інформатиці виникає ситуація, коли символи одного алфавіту треба подати символами іншого, тобто провести операцію кодування. Якщо кількість символів алфавіту, що кодується, менше кількості символів кодуючого алфавіту, то сама операція кодування не є складною, в іншому випадку необхідно використовувати фіксований набір символів кодуючого алфавіту для однозначного правильного кодування.

Як показала практика, найбільш простим алфавітом, що дозволяє кодувати інші алфавіти, є двійковий, що складається з двох символів, які позначаються, як правило, через 0 і 1. За допомогою n символів двійкового алфавіту можна закодувати 2п символів, а цього достатньо, щоб закодувати будь-який алфавіту.

Розмір, який може бути представлений символом двійкового алфавіту, називається мінімальної одиницею інформації чи бітом. Послідовність із 8 біт - байт. Алфавіт, що містить 256 різних 8-бітових послідовностей, називається байтовим.

Як стандартний сьогодні в інформатиці прийнято код, в якому кожен символ кодується 1 байтом. Існують інші алфавіти.

2. Системи числення

Під системою числення мається на увазі набір правил найменування та запису чисел. Розрізняють позиційні та непозиційні системи числення.

Система числення називається позиційною, якщо значення цифри числа залежить від розташування цифри числа. Інакше вона називається непозиційною. Значення числа визначається за становищем цих цифр у числі.

3. Подання чисел в ЕОМ

32-розрядні процесори можуть працювати з оперативною пам'яттю ємністю до 232-1, а адреси можуть записуватися в діапазоні 00000000 – FFFFFFFF. Однак у реальному режимі процесор працює з пам'яттю до 220-1, а адреси потрапляють у діапазон 00000 – FFFFF. Байти пам'яті можуть поєднуватися в поля як фіксованої, так і змінної довжини. Словом називається поле фіксованої довжини, що складається з 2 байтів, подвійним словом – поле з 4 байтів. Адреси полів бувають парні та непарні, при цьому для парних адрес операції виконуються швидше.

Числа з фіксованою точкою в ЕОМ представляються як цілі двійкові числа, і об'єм, що займається ними, може становити 1, 2 або 4 байти.

Цілі двійкові числа подаються у додатковому коді, відповідно числа з фіксованою точкою подаються у додатковому коді. У цьому якщо число займає 2 байти, то структура числа записується за таким правилом: старший розряд відводиться під знак числа, інші - під двійкові цифри числа. Додатковий код позитивного числа дорівнює самому числу, а додатковий код від'ємного числа може бути отриманий за такою формулою: x = 10і - \х \, де n - розрядність числа.

У двійковій системі числення додатковий код виходить шляхом інверсії розрядів, тобто заміною одиниць нулями і навпаки, і додаванням одиниці до молодшого розряду.

Кількість бітів мантиси визначає точність уявлення чисел, кількість бітів машинного порядку визначає діапазон уявлення чисел з плаваючою точкою.

4. Формалізоване поняття алгоритму

Алгоритм може існувати лише тоді, коли в той же час існує певний математичний об'єкт. Формалізоване поняття алгоритму пов'язані з поняттям рекурсивних функцій, нормальних алгоритмів Маркова, машин Тьюринга.

У математиці функція називається однозначною, якщо будь-якого набору аргументів існує закон, яким визначається єдине значення функції. Як такий закон може виступати алгоритм; у цьому випадку функція називається обчислюваною.

Рекурсивні функції - це підклас функцій, що обчислюються, а алгоритми, що визначають обчислення, називаються супутніми алгоритмами рекурсивних функцій. Спочатку фіксуються базові рекурсивні функції, котрим супутній алгоритм тривіальний, однозначний; потім вводяться три правила - оператори підстановки, рекурсії та мінімізації, за допомогою яких на основі базових функцій виходять складніші рекурсивні функції.

Базовими функціями та їх супутніми алгоритмами можуть виступати:

1) функція n незалежних змінних, тотожно дорівнює нулю. Тоді, якщо знаком функції є n, то незалежно від кількості аргументів значення функції слід покласти рівним нулю;

2) тотожна функція n незалежних змінних видів ψni. Тоді, якщо знаком функції є ni, то значенням функції слід взяти значення i-го аргументу, вважаючи зліва направо;

3) Λ – функція одного незалежного аргументу. Тоді, якщо знаком функції є λ, значенням функції слід взяти значення, наступне за значенням аргументу. Різні вчені пропонували свої підходи до формалізованого

подання алгоритму. Наприклад, американський вчений Черч припустив, що клас функцій, що обчислюються, вичерпується рекурсивними функціями і, як наслідок, яким би не був алгоритм, що переробляє один набір цілих невід'ємних чисел в інший, знайдеться алгоритм, супутній рекурсивної функції, еквівалентний даному. Отже, якщо вирішення деякої поставленої завдання не можна побудувати рекурсивну функцію, те й немає алгоритму її вирішення. Інший вчений, Т'юрінг, розробив віртуальну ЕОМ, яка переробляла вхідну послідовність символів у вихідну. У зв'язку з цим їм висунули тезу, що будь-яка обчислювана функція обчислювана за Тьюрингу.

ЛЕКЦІЯ № 2. Мова Pascal

1. Введення в мову Pascal

Основні символи мови - літери, цифри та спеціальні символи - становлять його алфавіт. Мова Pascal включає наступний набір основних символів:

1) 26 латинських малих і 26 латинських великих літер:

ABCDEFGHIJKLMNOPQRSTUVWXYZ

abcdefghijklmnopqrstuvwxyz;

2) _ (знак підкреслення);

3) 10 цифр: 0123456789;

4) знаки операцій:

+ - x / = <> < > <= >= := @;

5) обмежувачі:

., ' ( ) [ ] (..) { } (* *).. : ;

6) специфікатори: ^ # $;

7) службові (зарезервовані) слова:

ABSOLUTE, ASSEMBLER, AND, ARRAY, ASM, BEGIN, CASE, CONST, CONSTRUCTOR, DESTRUCTOR, DIV, DO, DOWNTO, ELSE, END, EXPORT, EXTERNAL, FAR, FILE, FOR, FORWARD, FUNCTION, GOTO, IF, IMPLEMENTATION, IN, INDEX, INHERITED, INLINE, INTERFACE, INTERRUPT, LABEL, LIBRARY, MOD, NAME, NIL, NEAR, NOT, OBJECT, OF, OR, PACKED, PRIVATE, PROCEDURE, PROGRAM, PUBLIC, RECORD, REPEAT, RESIDENT, SET, SHL, SHR, STRING, THEN, TO, TYPE, UNIT, UNTIL, USES, VAR, VIRTUAL, WHILE, WITH, XOR.

Крім перерахованих, набір основних символів входить пробіл. Пробіли не можна використовувати всередині здвоєних символів та зарезервованих слів.

Концепція типу для даних

У математиці прийнято класифікувати змінні відповідно до деяких важливих характеристик. Проводиться суворе розмежування між речовими, комплексними та логічними змінними, між змінними, що представляють окремі значення та безліч значень, і т. д. При обробці даних на ЕОМ така класифікація ще важливіша. У будь-якій алгоритмічній мові кожна константа, змінна, вираз чи функція бувають певного типу.

У мові Pascal існує правило: тип явно задається в описі змінної або функції, що передує їх використанню. Концепція типу мови Pascal має такі основні властивості:

1) будь-який тип даних визначає безліч значень, до якого належить константа, які може приймати змінна або вираз або виробляти операція чи функція;

2) тип значення, що задається константою, змінною або виразом, можна визначити за їх видом або описом;

3) кожна операція чи функція вимагають аргументів фіксованого типу та видають результат фіксованого типу.

Звідси випливає, що транслятор може використовувати інформацію про типи для перевірки обчислюваності та правильності різних конструкцій.

Тип визначає:

1) можливі значення змінних, констант, функцій, виразів, що належать до цього типу;

2) внутрішню форму подання даних у ЕОМ;

3) операції та функції, які можуть виконуватися над величинами, що належать до цього типу.

Слід зазначити, що обов'язковий опис типу призводить до надмірності у тексті програм, але така надмірність є важливим допоміжним засобом розробки програм і як необхідне властивість сучасних алгоритмічних мов високого рівня.

У мові Pascal існують скалярні та структуровані типи даних. До скалярних типів відносяться стандартні типи та типи, що визначаються користувачем. Стандартні типи включають цілі, дійсні, символьні, логічні та адресні типи.

Цілі типи визначають константи, змінні та функції, значення яких реалізуються безліччю цілих чисел, допустимих у цій ЕОМ.

Дійсні типи визначає ті дані, що реалізуються підмножиною дійсних чисел, допустимих у цій ЕОМ.

Типи, що визначаються користувачем, - перерахований та інтервальний. Структуровані типи мають чотири різновиди: масиви, множини, записи та файли.

Крім перерахованих, Pascal включає ще два типи – процедурний та об'єктний.

Вираз мови складається з констант, змінних, покажчиків функцій, знаків операцій та дужок. Вираз визначає правило обчислення деякого значення. Порядок обчислення визначається старшинством (пріоритетом) операцій, що містяться в ньому. У мові Pascal прийнято наступний пріоритет операцій:

1) обчислення у круглих дужках;

2) обчислення значень функцій;

3) унарні операції;

4) операції *, /, div, mod, and;

5) операції +, -, or, xor;

6) операції відносини =, <>, <, >, <=, >=.

Вирази входять до складу багатьох операторів мови Pascal, також можуть бути аргументами вбудованих функцій.

2. Стандартні процедури та функції

Арифметичні функції

1. Function Abs(X);

Повертає повне значення параметра.

X - вираз речового або цілісного типу.

2. Function ArcTan(X: Extended): Extended;

Повертає Арктангенс аргументу.

X - вираз речового або цілісного типу.

3. Function Ехр(Х: Real): Real;

Повертає експоненту.

X - вираз речового або цілісного типу.

4. Function Frac(X: Real): Real;

Повертає дрібну частину аргументу.

X - вираз речового типу. Результат - дрібна частина X, тобто.

Frac(X) = X-Int(X).

5. Function Int(X: Real): Real;

Повертає цілу частину аргументу.

X - вираз речового типу. Результат - ціла частина X, тобто X, округлений до нуля.

6. Function Ln(X: Real): Real;

Повертає натуральний логарифм (Ln е = 1) виразу X речового типу.

7. Function Pi: Extended;

Повертає значення Pi, визначене як 3.1415926535.

8. Function Sin(X: Extended): Extended;

Повертає синус аргументу.

X - вираз речового типу. Sin повертає синус кута X у радіанах.

9. Function Sqr(X: Extended): Extended;

Повертає квадрат аргументу.

X - вираз із плаваючою комою. Результат того самого типу, що і X.

10. Function Sqrt(X: Extended): Extended;

Повертає квадратний корінь аргументу.

X - вираз із плаваючою комою. Результат – квадратний корінь X.

Процедури та функції перетворення величин

1. Procedure Str(X[: Width[: Decimals]]; var S);

Перетворює число X на рядкове подання згідно

Width та параметри форматування Decimals. X - вираз речового або цілого типу. Width і Decimals – вирази цілого типу. S - змінна типу String або символьний масив із нульовим закінченням, якщо допускається розширений синтаксис.

2. Function Chr (X: Byte): Char;

Повертає символ із порядковим номером X у ASCII-таблиці.

3. Function High(X);

Повертає найбільше значення у діапазоні параметра.

4. Function Low(X);

Повертає найменше значення діапазону параметра.

5. Function Ord(X): Longint;

Повертає порядкове значення виразу перерахованого типу. X - вираз перелічуваного типу.

6. Function Round (X: Extended): Longint;

Округлює значення речового типу до цілого. X - вираз речового типу. Round повертає значення Longint, яке є значенням X, заокругленим до найближчого цілого числа. Якщо X знаходиться точно посередині між двома цілими числами, число повертається з найбільшою абсолютною величиною. Якщо округлене значення X виходить за діапазон Longint, генерується помилка часу виконання програми, яку можна обробити з використанням виняткової ситуації EInvalidOp.

7. Function Trunc (X: Extended): Longint;

Усікає значення речовинного типу до цілого. Якщо округлене значення X виходить за діапазон Longint, генерується помилка часу виконання програми, яку можна обробити з використанням виняткової ситуації EInvalidOp.

8. Procedure Val(S; var V; var Code: Integer);

Перетворює число з рядкового значення S на числове

уявлення V. S - вираз рядкового типу - послідовність символів, що формує ціле чи речове число. Якщо вираз S неприпустимий, індекс неправильного символу зберігається у змінній Code. В іншому випадку Code встановлюється на нуль.

Процедури та функції роботи з порядковими величинами

1. Procedure Dec(varX [; N: Longlnt]);

Віднімає одиницю або N із змінної X. Dec(X) відповідає X:= X - 1, і Dec(X, N) відповідає X:= X - N. X - змінна перерахованого типу або типу PChar, якщо допускається розширений синтаксис, і N - вираз цілого типу. Процедура Dec генерує оптимальний код і особливо корисна у тривалих циклах.

2. Procedure Inc(varX [; N: Longlnt]);

Додає одиницю або N до змінної X. X - змінна типу або типу PChar, якщо допускається розширений синтаксис, і N - вираз цілого типу. Inc (X) відповідає інструкції X:= X + 1 і Inc (X, N) відповідає інструкції X:= X + N. Процедура Inc генерує оптимальний код і особливо корисна в тривалих циклах.

3. Function Odd(X: Longlnt): Boolean;

Повертає True, якщо X - непарне число, і False - інакше.

4. Function Pred(X);

Повертає попереднє значення параметра. X - вираз перелічуваного типу. Результат того самого типу.

5. Function Succ(X);

Повертає значення параметра. X - вираз перелічуваного типу. Результат того самого типу.

3. Оператори мови Pascal

Умовний оператор

Формат повного умовного оператора визначається так: If В then SI else S2; де У - умова розгалуження (ухвалення рішення), логічний вираз чи ставлення; SI, S2 - один оператор, що виконується, простий або складовий.

При виконанні умовного оператора спочатку обчислюється вираз, потім аналізується його результат: якщо В - істинно, то виконується оператор S1 - гілка then, а оператор S2 пропускається; якщо В - хибно, то виконується оператор S2 - гілка else, а оператор S1 - пропускається.

Також існує скорочена форма умовного оператора. Вона записується як: If В then S.

оператор вибору

Структура оператора має такий вигляд:

case S of

c1: insruction1;

c2: insruction2;

...

cn: insructionN;

else instruction

end;

де S – вираз порядкового типу, значення якого обчислюється;

с1, с2...., сп - константи порядкового типу, з якими порівнюються вирази

S; instruction1,..., instructionN - оператори, з яких виконується той, з константою якого збігається значення виразу S;

instruction - оператор, який виконується, якщо значення виразу Sylq збігається з жодною з констант c1, с2 .... сn.

Цей оператор є узагальненням умовного оператора If довільного числа альтернатив. Існує скорочена форма оператора, за якої гілка else відсутня.

Оператор циклу із параметром

Оператори циклу з параметром, які починаються зі слова for, викликають виконання оператора, що повторюється, який може бути складовим оператором, поки керуючої змінної присвоюється зростаюча послідовність значень.

Загальний вигляд оператора:

for <лічильник циклу> := <початкове значення> to <кінцеве значення> do <оператор>;

Коли починає виконуватися оператор for, початкове та кінцеве значення визначаються один раз, і ці значення зберігаються протягом усього виконання оператора for. Оператор, який міститься в тілі оператора for, виконується один раз для кожного значення діапазону між початковим і кінцевим значенням. Лічильник циклу завжди ініціалізується початковим значенням. Коли оператор працює, значення лічильника циклу збільшується при кожному повторенні на одиницю. Якщо початкове значення перевищує кінцеве значення, то оператор, що міститься в тілі оператора, не виконується. Коли в операторі циклу використовується ключове слово downto, значення змінної, що управляє, зменшується при кожному повторенні на одиницю. Якщо початкове значення в такому операторі менше, ніж кінцеве значення, то цикл, що міститься в тілі оператора, оператор не виконується.

Якщо оператор, що міститься в тілі оператора for, змінює значення лічильника циклу, це помилка. Після виконання оператора for значення керуючої змінної стає невизначеним, якщо виконання оператора for не було перервано за допомогою оператора переходу.

Оператор циклу з передумовою

Оператор циклу з передумовою (починається з ключового слова while) містить у собі вираз, що управляє повторним виконанням оператора (який може бути складовим оператором). Форма циклу:

While B do S;

де B - логічна умова, істинність якої перевіряється (вона є умовою завершення циклу);

S – тіло циклу – один оператор.

Вираз, за ​​допомогою якого здійснюється керування повторенням оператора, повинен мати логічний тип. Обчислення його провадиться до того, як внутрішній оператор буде виконаний. Внутрішній оператор виконується повторно доти, доки вираз набуває значення Труе. Якщо вираз від початку приймає значення False, то оператор, що міститься всередині оператора циклу з передумовою, не виконується.

Оператор циклу з постумовою

В операторі циклу з постумовою (починається зі слова repeat) вираз, який керує повторним виконанням послідовності операторів, міститься всередині оператора repeat. Форма циклу:

repeat S until B;

де B - логічна умова, істинність якої перевіряється (вона є умовою завершення циклу);

S – один або більше операторів тіла циклу.

Результат виразу має бути логічного типу. Оператори, укладені між ключовими словами repeat і until, виконуються послідовно доти, доки результат виразу не прийме значення True. Послідовність операторів виконається принаймні один раз, оскільки обчислення виразу проводиться після кожного виконання послідовності операторів.

ЛЕКЦІЯ № 3. Процедури та функції

1. Поняття допоміжного алгоритму

Алгоритм розв'язання задачі проектується шляхом декомпозиції всього завдання окремі подзадачи. Зазвичай підзавдання реалізуються як підпрограм.

Підпрограма - це деякий допоміжний алгоритм, що багаторазово використовується в основному алгоритмі з різними значеннями деяких вхідних величин, які називаються параметрами.

Підпрограма в мовах програмування – це послідовність операторів, які визначені та записані лише в одному місці програми, однак їх можна викликати для виконання з однієї або кількох точок програми. Кожна підпрограма визначається унікальним іменем.

У мові Pascal існують два типи підпрограм – процедури та функції. Процедура та функція – це іменована послідовність описів та операторів. При використанні процедур або функцій програма повинна містити текст процедури або функції та звернення до процедури або функції. Параметри, вказані в описі, називаються формальними, вказані у зверненні підпрограми - фактичними. Усі формальні параметри можна розбити на наступні категорії:

1) параметри-змінні;

2) параметри-константи;

3) параметри-значення;

4) параметри-процедури та параметри-функції, тобто параметри процедурного типу;

5) нетипізовані параметри-змінні.

Тексти процедур та функцій містяться в розділі описів процедур та функцій.

Передача імен процедур та функцій як параметри

У багатьох завданнях, особливо у задачах обчислювальної математики, необхідно передавати імена процедур та функцій як параметри. Для цього в TURBO PASCAL введено новий тип даних – процедурний, або функціональний, залежно від того, що описується. (Опис процедурних та функціональних типів наведено у розділі опису типів.)

Функціональний та процедурний тип визначається як заголовок процедури та функції зі списком формальних параметрів, але без імені. Можна визначити функціональний або процедурний тип без параметрів, наприклад:

тип

Proc = Procedure;

Після оголошення процедурного або функціонального типу його можна використовувати для опису формальних параметрів - імен процедур і функцій. Крім того, необхідно написати ті реальні процедури або функції, імена яких передаватимуться як фактичні параметри.

2. Процедури Pascal

Кожен опис процедури містить заголовок, за яким слідує програмний блок. Загальний вигляд заголовка процедури наступний:

Procedure <ім'я> [(<список формальних параметрів>)];

Процедура активізується за допомогою оператора процедури, де містяться ім'я процедури та необхідні параметри. Оператори, які мають виконуватися під час запуску процедури, містяться в операторній частині модуля процедури. Якщо в операторі, що міститься в процедурі всередині модуля процедури використовується ідентифікатор процедури, то процедура буде виконуватися рекурсивно, тобто буде при виконанні звертатися сама до себе.

3. Функції у Pascal

Опис функції визначає частину програми, в якій обчислюється та повертається значення. Загальний вигляд заголовка функції наступний:

Function <ім'я > [(<список формальних параметрів>)]: <тип результату, що повертається >;

Функція активується під час її виклику. Під час виклику функції вказуються ідентифікатор функції та будь-які параметри, необхідні для обчислення. Виклик функції може включатися у вирази як операнда. Коли вираз обчислюється, функція виконується і значення операнда стає значення, що повертається функцією.

У операторній частині блоку функції задаються оператори, які мають виконуватися під час активізації функції. У модулі повинен утримуватися принаймні один оператор присвоювання, в якому ідентифікатору функції присвоюється значення. Результатом функції є останнє значення. Якщо такий оператор присвоювання відсутня або він не був виконаний, то значення, яке повертається функцією, не визначено.

Якщо ідентифікатор функції використовується під час виклику функції всередині модуля, функція виконується рекурсивно.

4. Випереджувальні описи та підключення підпрограм. Директива

У програмі може бути кілька підпрограм, т. е. структура програми може бути ускладнена. Однак ці підпрограми можуть розташовуватися на одному рівні вкладеності, тому спочатку має йти опис підпрограми, а потім звернення до неї, якщо не використовується спеціальний випереджальний опис.

Опис процедури, що містить замість блоку операторів директиву forward, називається випереджаючим описом. У будь-якому місці після цього опису за допомогою визначального опису процедура має визначатися. Визначальний опис - це опис, в якому використовується той же ідентифікатор процедури, але опущений список формальних параметрів, і включений блок операторів. Опис forward та визначальний опис повинні бути присутніми в одній і тій же частині опису процедури та функції. Між ними можуть описуватися інші процедури та функції, які можуть звертатися до процедури з випереджаючим описом. Таким чином, можлива взаємна рекурсія.

Випереджальний опис та визначальний опис є повним описом процедури. Процедура вважається описаною за допомогою випереджального опису.

Якщо програмі буде утримуватися чимало підпрограм, то програма перестане бути наочною, у ній важко орієнтуватися. Щоб уникнути цього, деякі підпрограми зберігають у вигляді вихідних файлів на диску, а при необхідності вони підключаються до основної програми на етапі компіляції за допомогою директиви компіляції.

Директива – це спеціальний коментар, який може бути розміщений у будь-якому місці програми, там, де може знаходитись і звичайний коментар. Однак вони відрізняються тим, що директива має спеціальну форму запису: відразу після закриває дужки без пропуску записується знак S, а потім, знову ж таки без пропуску, вказується директива.

Приклад

1) {SE+} – емулювати математичний співпроцесор;

2) {SF+} -формувати далекий тип виклику процедур та функцій;

3) {SN+} – використовувати математичний співпроцесор;

4) {SR+} - перевіряти вихід за межі діапазонів.

Деякі ключі компіляції можуть містити параметр, наприклад:

{$1 ім'я файлу} - включити в текст програми, що компілюється, названий файл.

ЛЕКЦІЯ №4. Підпрограми

1. Параметри підпрограм

У описі процедури чи функції задається список формальних параметрів. Кожен параметр, описаний у списку формальних параметрів, є локальним по відношенню до процедури або функції, що описується, і в модулі, пов'язаному з даною процедурою або функцією, на нього можна посилатися за його ідентифікатором.

Існують три типи параметрів: значення, змінна та нетипізована змінна. Вони характеризуються наступним.

1. Група параметрів без попереднього ключового слова є список параметрів-значень.

2. Група параметрів, перед якими слідує ключове слово const і за якими слідує тип, є списком параметрів-констант.

3. Група параметрів, перед якими стоїть ключове слово var і за якими слідує тип, є списком нетипізованих параметрів-змінних.

4. Група параметрів, перед якими стоїть ключове слово var або const, за якими не слідує тип, є списком нетипізованих параметрів-змінних.

2. Типи параметрів підпрограм

Параметри-значення

Формальний параметр-значення обробляється як локальна стосовно процедури або функції змінна, за винятком того, що він отримує своє початкове значення з відповідного фактичного параметра при активізації процедури або функції. Зміни, які зазнає формального параметра-значення, не впливають на значення фактичного параметра. Відповідне фактичне значення параметра значення має бути виразом, і його значення не повинно мати файловий тип або будь-який структурний тип, що містить у собі файловий тип.

Фактичний параметр повинен мати тип, сумісний для присвоєння з типом формального параметра-значення. Якщо параметр має рядковий тип, формальний параметр матиме атрибут розміру, рівний 255.

Параметри-константи

Формальні параметри-константи працюють аналогічно до локальної змінної, доступної тільки за читанням, яка отримує своє значення при активізації процедури або функції від відповідного фактичного параметра. Присвоєння формальному параметру-константі не допускаються. Формальний параметр-константа також не може передаватися як фактичний параметр іншій процедурі або функції. Параметр-константа, який відповідає фактичному параметру в операторі процедури або функції, повинен підкорятися тим же правилам, що й фактичне значення параметра.

У тих випадках, коли формальний параметр не змінює при виконанні процедури або функції свого значення, замість параметра значення слід використовувати параметр-константу. Параметри-константи дозволяють під час реалізації процедури чи функції захиститися від випадкових присвоєнь формальному параметру. Крім того, для параметрів структурного та рядкового типу компілятор при використанні замість параметрів-значень параметрів-констант може генерувати ефективніший код.

Параметри-змінні

Параметр-змінна використовується, коли значення має передаватися з процедури або функції програмі, що викликає. Відповідний фактичний параметр оператора виклику процедури або функції повинен бути посиланням на змінну. При активізації процедури або функції формальний параметр-змінна замінюється фактичною змінною, будь-які зміни у значенні формального параметра-змінної відбиваються на фактичному параметрі.

Всередині процедури або функції будь-яке посилання на формальний параметр-змінну призводить до доступу до фактичного параметра. Тип фактичного параметра повинен збігатися з типом формального параметра-змінної, але це обмеження можна обійти за допомогою нетипізованого параметра-змінної).

Нетипізовані параметри

Коли формальний параметр є нетипізованим параметром-змінною, то відповідний фактичний параметр може бути будь-яким посиланням на змінну або константу незалежно від її типу. Нетипізований параметр, описаний з ключовим словом var, може бути модифікований, а нетипізований параметр, описаний з ключовим словом const, доступний тільки за читанням.

У процедурі або функції у нетипізованого параметра-змінної тип відсутній, тобто він несумісний зі змінними всіх типів, доки йому не буде присвоєно певний тип за допомогою присвоєння типу змінної.

Хоча нетипізовані параметри дають більшу гнучкість, їх використання пов'язане з певним ризиком. Компілятор не може перевірити допустимість операцій із нетипізованими змінними.

Процедурні змінні

Після визначення процедурного типу з'являється можливість описувати змінні цього. Такі змінні називають процедурними змінними. Як і ціла змінна, якій можна надати значення цілого типу, процедурної змінної можна надати значення процедурного типу. Таким значенням може бути, звичайно, інша процедурна змінна, але воно може також бути ідентифікатором процедури або функції. У такому контексті опис процедури або функції можна розглядати як опис особливого роду константи, значенням якої є процедура або функція.

Як і при будь-якому іншому присвоюванні, значення змінної в лівій і правій частині повинні бути сумісні з присвоєння. Процедурні типи, щоб вони були сумісні за присвоєнням, повинні мати те саме число параметрів, а параметри на відповідних позиціях повинні бути однакового типу. Імена параметрів в описі процедурного типу жодної дії не викликають.

Крім того, для забезпечення сумісності щодо присвоєння процедура або функція, якщо її потрібно привласнити процедурній змінній, повинна відповідати таким вимогам:

1) це не повинна бути стандартна процедура чи функція;

2) така процедура чи функція не може бути вкладеною;

3) така процедура має бути процедурою типу inline;

4) вона має бути процедурою переривання (interrupt).

Стандартними процедурами та функціями вважаються процедури та функції, описані в модулі System, такі як Writeln, Readln, Chr, Ord. Не можна використовувати вкладені процедури та функції з процедурними змінними. Процедура або функція вважається вкладеною, коли вона описується всередині іншої процедури чи функції.

Використання процедурних типів не обмежується просто процедурними змінними. Як і будь-який інший тип, процедурний тип може брати участь у описі структурного типу.

Коли процедурної змінної присвоюється значення процедури, то фізично відбувається таке: адреса процедури зберігається в змінній. Фактично процедурна змінна дуже нагадує змінну-покажчик, тільки замість посилання на дані вона вказує на процедуру чи функцію. Як і покажчик, процедурна змінна займає 4 байти (два слова), у яких міститься адреса пам'яті. У першому слові зберігається усунення, у другому - сегмент.

Параметри процедурного типу

Оскільки процедурні типи допускається використовувати в будь-якому контексті, можна описувати процедури або функції, які сприймають процедури і функції як параметри. Параметри процедурного типу особливо корисні у разі, коли над безліччю процедур чи функцій потрібно виконати якісь спільні дії.

Якщо процедура або функція повинні передаватися як параметр, вони повинні задовольняти ті самі правила сумісності типу, що і при присвоєнні. Тобто такі процедури або функції повинні компілюватися з директивою far, вони не можуть бути вбудованими функціями, не можуть бути вкладеними та не можуть описуватися з атрибутами inline або interrupt.

ЛЕКЦІЯ № 5. Рядковий тип даних

1. Рядковий тип у Pascal

Послідовність символів певної довжини називається рядком. Змінні рядкового типу визначаються шляхом вказівки імені змінної, зарезервованого слова string, і можливо, але не обов'язково вказівки максимального розміру, тобто довжини рядка у квадратних дужках. Якщо не задавати максимальний розмір рядка, то за умовчанням він дорівнює 255, тобто рядок складатиметься з 255 символів.

До кожного елемента рядка можна звернутися за його номером. Однак введення та виведення рядків здійснюються повністю, а не поелементно, як це відбувається в масивах. Число введених символів не повинно перевищувати вказаного в максимальному розмірі рядка, так якщо таке перевищення буде, то "зайві" символи будуть проігноровані.

2. Процедури та функції для змінних рядкового типу

1. Function Copy(S: String; Index, Count: Integer): String;

Повертає рядок рядок. S – вираз типу String.

Index і Count – вирази цілого типу. Функція повертає рядок, що містить Count символів, що починаються з позиції Index. Якщо Index більший за довжину S, функція повертає порожній рядок.

2. Procedure Delete (var S: String; Index, Count: Integer);

Видаляє підстроку символів довжиною Count із рядка S, починаючи з позиції Index. S – змінна типу String. Index і Count – вирази цілого типу. Якщо Index більший за довжину S, символи не видаляються.

3. Procedure Insert(Source: String; var S: String; Index: Integer);

Поєднує підрядок у рядок, починаючи з певної позиції. Source – вираз типу String. S – змінна типу String будь-якої довжини. Index - вираз цілого типу. Insert вставляє Source S, починаючи з позиції S[Index].

4. Function Length(S: String): Integer;

Повертає число символів, що фактично використовується в рядку S. Зверніть увагу: при використанні рядків з нуль-закінченням число символів не обов'язково дорівнює числу байтів.

5. Function Pos(Substr: String; S: String): Integer;

Шукає підрядок у рядку. Pos шукає Substr усередині S і повертає ціле значення, яке є індексом першого символу Substr усередині S. Якщо Substr не знайдено, Pos повертає нуль.

3. Записи

Запис є сукупність обмеженого числа логічно пов'язаних компонентів, що належать до різних типів. Компоненти запису називаються полями, кожне з яких визначається ім'ям. Поле запису містить ім'я поля, за яким через двокрапку вказується тип цього поля. Поля запису можуть відноситися до будь-якого типу, допустимого в Pascal, за винятком файлового типу.

Опис запису в мові Pascal здійснюється за допомогою службового слова RECORD, за яким описуються компоненти запису. Завершується опис запису службовим словом END.

Наприклад, записник містить прізвища, ініціали та номери телефону, тому окремий рядок у записнику зручно представити у вигляді наступного запису:

type Row = Record

FIO: String[20];

TEL: String[7];

end;

var str: Row;

Опис записів можливий і без використання імені типу, наприклад:

var str: Record

FIO: String[20];

TEL: String[7];

end;

Звертання до запису загалом допускається лише операторах присвоювання, де ліворуч і праворуч від знака присвоювання використовуються імена записів однакового типу. У решті випадків оперують окремими полями записів. Щоб звернутися до окремого компонента запису, необхідно вказати ім'я запису та через точку вказати ім'я потрібного поля. Таке ім'я називається складовим. Компонентом запису може бути запис, у такому разі складене ім'я міститиме не два, а більшу кількість імен.

Звернення до компонентів записів можна спростити, якщо скористатися оператором приєднання with. Він дозволяє замінити складові імена, що характеризують кожне поле просто на імена полів, а ім'я запису визначити в операторі приєднання.

Іноді вміст окремого запису залежить від значення одного з його полів. У мові Pascal допускається опис запису, що складається із загальної та варіантної частин. Варіантна частина визначається за допомогою конструкції case Р of, де Р - ім'я поля із загальної частини запису. Можливі значення, що приймаються цим полем, перераховуються так само, як і в операторі варіанта. Однак замість вказівки виконуваної дії, як це робиться в операторі варіанта, вказуються поля варіанта, укладені у круглі дужки. Опис варіантної частини завершується службовим словом end. Тип поля Р можна вказати у заголовку варіантної частини. Ініціалізація записів здійснюється за допомогою типізованих констант.

4. Безліч

Поняття множини в мові Pascal ґрунтується на математичному уявленні про множини: це обмежена сукупність різних елементів. Для побудови конкретного множинного типу використовується перерахований або інтервальний тип даних. Тип елементів, що становлять безліч, називається базовим типом.

Множинний тип описується за допомогою службових слів Set of, наприклад:

type M = Set of;

Тут М – множинний тип, В – базовий тип.

Приналежність змінних до множинного типу може бути визначена у розділі опису змінних.

Константи множинного типу записуються у вигляді укладеної у квадратні дужки послідовності елементів або інтервалів базового типу, розділених комами. Константа виду [] означає порожнє підмножина.

Безліч включає набір елементів базового типу, всі підмножини даної множини, а також порожнє підмножина. Якщо базовий тип, у якому будується безліч, має До елементів, то число підмножин, які входять у це безліч, дорівнює 2 ступеня До. Порядок перерахування елементів базового типу константах байдужий. Значення змінної множини може бути задане конструкцією виду [Т], де Т - змінна базового типу.

До змінних і константів множини застосовні операції присвоєння (:=), об'єднання (+), перетину (*) і віднімання (-). Результат виконання цих операцій є величина множинного типу:

1) ['A','B'] + ['A','D'] дасть ['A','B','D'];

2) ['A'] * ['A','B','C'] дасть ['A'];

3) ['A','B','C'] - ['A','B'] дасть ['C'].

До множинних величин застосовні операції: тотожність (=), нетотожність (<>), міститься в (<=), містить (>=). Результат виконання цих операцій має логічний тип:

1) ['A','B'] = ['A','C'] дасть FALSE;

2) ['A','B'] <> ['A','C'] дасть TRUE;

3) ['B'] <= ['B','C'] дасть TRUE;

4) ['C','D'] >= ['A'] дасть FALSE.

Крім цих операцій, для роботи з величинами множинного типу використовується операція in, що перевіряє належність елемента базового типу, що стоїть ліворуч від знака операції, множині, що стоїть праворуч від знака операції. Результат виконання цієї операції – булевський. Операція перевірки приналежності елемента часто використовується замість операцій відносини.

При використанні в програмах даних множини виконання операцій відбувається над бітовими рядками даних. Кожному значенню множини в пам'яті ЕОМ відповідає один двійковий розряд.

Величини множини не можуть бути елементами списку введення-виводу. У кожній конкретній реалізації транслятора з мови Pascal кількість елементів базового типу, на якому будується безліч, обмежена.

Ініціалізація величин множини здійснюється за допомогою типізованих констант.

Наведемо деякі процедури для роботи з безліччю.

1. Procedure Exclude (var S: Set of T; I: T);

Видаляє елемент I з множини S. S - змінна типу "множина", і I - вираз типу, сумісного з вихідним типом S. Конструкція Exclude(S, I) відповідає S: = S - [I], але генерує ефективніший код.

2. Procedure Include(var S: Set of T; I:T);

Додає елемент I до множини S. S - змінна типу "множина", і I - вираз типу, сумісного з типом S. Конструкція Include(S, I) відповідає S: = S + [I], але генерує ефективніший код.

лекція № 6. Файли

1. Файли. Операції з файлами

Введення файлового типу в мову Pascal викликане необхідністю забезпечити можливість роботи з периферійними (зовнішніми) пристроями ЕОМ, призначеними для введення, виведення та зберігання даних.

Файловий тип даних (або файл) визначає впорядковану сукупність довільної кількості однотипних компонентів. Загальна властивість масиву, множини і запису полягає в тому, що їх компонент визначено на етапі написання програми, тоді як кількість компонент файлу в тексті програми не визначається і може бути довільним.

Під час роботи з файлами виконуються операції вводу-виводу. Операція введення означає перепис даних із зовнішнього пристрою (з вхідного файлу) в основну пам'ять ЕОМ, операція виведення - це пересилання даних із основної пам'яті на зовнішній пристрій (вихідний файл). Файли зовнішніх пристроїв часто називають фізичними файлами. Їхні імена визначаються операційною системою.

У програмах Pascal імена файлів задаються за допомогою рядків. Для роботи з файлами у програмі необхідно визначити файлову змінну. Pascal підтримує три файлові типи: текстові файли, компонентні файли, безтипові файли.

Файлові змінні, описані у програмі, називають логічними файлами. Усі основні процедури та функції, що забезпечують введення-виведення даних, працюють тільки з логічними файлами. Фізичний файл повинен бути пов'язаний із логічним до виконання процедур відкриття файлів.

Текстові файли

Особливе місце в Pascal займають текстові файли, компоненти яких мають символьний тип. Для опису текстових файлів у мові визначено стандартний тип Text:

var TF1, TF2: Text;

Текстові файли є послідовність рядків, а рядки - послідовність символів. Рядки мають змінну довжину, кожен рядок завершується ознакою кінця рядка.

Компонентні файли

Компонентний, або типізований файл - це файл з оголошеним типом його компонент. Компонентні файли складаються з машинних уявлень змінних значень, вони зберігають дані в тому ж вигляді, що і пам'ять ЕОМ.

Опис величин файлового типу має вигляд:

type М = File Of Т;

де М – ім'я файлового типу;

Т – тип компоненти.

Компонентами файлу може бути все скалярні типи, та якщо з структурованих - масиви, множини, записи. Практично у всіх конкретних реалізаціях мови Pascal конструкція "файл файлів" неприпустима.

Усі операції над компонентними файлами виконуються за допомогою стандартних процедур.

Write(f,X1,X2,...XK)

Безтипові файли

Бестипові файли дозволяють записувати на диск довільні ділянки пам'яті ЕОМ і зчитувати їх із диска на згадку. Описуються безтипові файли так:

var f: File;

Тепер перерахуємо процедури та функції для роботи з різними видами файлів.

1. Procedure Assign(var F; FileName: String);

Процедура AssignFile зіставляє ім'я зовнішнього файла із файловою змінною.

F – файлова змінна будь-якого файлового типу, FileName – вираз типу String або вираз типу PChar, якщо допускається розширений синтаксис. Всі подальші операції з F виконуються із зовнішнім файлом.

Не можна використовувати процедуру з відкритою файловою змінною.

2. Procedure Close(var F);

Процедура розриває зв'язок між файловою змінною та зовнішнім дисковим файлом та закриває файл.

F - файлова змінна будь-якого файлового типу, відкрита процедурами Reset, Rewrite чи Append. Зовнішній файл пов'язаний з F повністю модифікується і потім закривається, звільняючи дескриптор файлу для повторного використання.

Директива {SI+} дозволяє обробляти помилки під час виконання програми, використовуючи обробку виняткових ситуацій. При вимкненій директиві {$1-} необхідно використовувати IOResult для перевірки помилок вводу-виводу.

3. Function Eof(var F): Boolean;

{Типізовані або нетипізовані файли}

Function Eof[(var F: Text)]: Boolean;

{Текстові файли}

Перевіряє, чи є поточна позиція файлу кінцем файлу.

Eof(F) повертає True, якщо поточна позиція файлу знаходиться за останнім символом файлу або якщо файл порожній; інакше Eof (F) повертає False.

Директива {SI+} дозволяє обробляти помилки під час виконання програми, використовуючи обробку виняткових ситуацій. При вимкненій директиві {SI-} необхідно використовувати IOResult для перевірки помилок введення-виведення.

4. Procedure Erase(var F);

Видаляє зовнішній файл, пов'язаний із F.

F – файлова змінна будь-якого файлового типу.

Перед викликом процедури Erase слід закрити файл.

Директива {SI+} дозволяє обробляти помилки під час виконання програми, використовуючи обробку виняткових ситуацій. При вимкненій директиві {SI-} необхідно використовувати IOResult для перевірки помилок введення-виведення.

5. Function FileSize(var F): Integer;

Повертає розмір у байтах файлу F Однак, якщо F - типізований файл, FileSize поверне кількість записів у файлі. Перед використанням функції FileSize файл має бути відкритим. Якщо файл порожній, FileSize(F) повертає нуль. F – змінна будь-якого файлового типу.

6. Function FilePos(var F): Longlnt;

Повертає поточну позицію файлу всередині файлу.

Перед використанням функції FilePos файл повинен бути відкритий. FilePos не використовується з текстовими файлами. F - змінна файлового типу, крім типу Text.

7. Procedure Reset (var F [: File; RecSize: Word]);

Відкриває наявний файл.

F - змінна будь-якого файлового типу, пов'язаного із зовнішнім файлом за допомогою AssignFile. RecSize – необов'язковий вираз, який використовується, якщо F – нетипізований файл. Якщо F - нетипізований файл, RecSize визначає розмір запису, який використовується для передачі даних. Якщо RecSize опущено, за замовчуванням розмір запису дорівнює 128 байтів.

Процедура Reset відкриває існуючий зовнішній файл, асоційований з файловою змінною F. Якщо зовнішнього файла з такою назвою немає, виникає помилка часу виконання. Якщо файл, пов'язаний з F, вже відкритий, він спочатку закривається, а потім знову відкривається. Поточна позиція файлу встановлюється на початок файлу.

8. Procedure Rewrite(var F: File [; Recsize: Word]);

Створює та відкриває новий файл.

F - змінна файлового типу, пов'язаного із зовнішнім файлом з використанням AssignFile. RecSize – необов'язковий вираз, який використовується, якщо F – нетипізований файл. Якщо F - нетипізований файл, RecSize визначає розмір запису, який використовується для передачі даних. Якщо RecSize опущено, за замовчуванням розмір запису дорівнює 128 байтів.

Процедура Rewrite створює новий зовнішній файл з ім'ям, пов'язаним з F Якщо зовнішній файл з тим самим ім'ям вже існує, він видаляється, і створюється новий порожній файл.

9. Procedure Seek(var F; N: Longlnt);

Переміщує поточну позицію файлу до певного компонента. Можна використовувати процедуру лише з відкритими типізованими або нетипізованими файлами.

Поточна позиція файлу F переміщується до номера N. Номер першого компонента файлу – 0.

Інструкція Seek(F, FileSize(F)) переміщує поточну позицію файла до кінця файлу.

10. Procedure Append(var F: Text);

Відкриває існуючий текстовий файл для додавання інформації до кінця файлу (дозапис).

Якщо зовнішнього файлу з цим ім'ям немає, відбувається помилка часу виконання. Якщо файл F вже відкритий, він закривається та знову відкривається. Поточна позиція файлу встановлюється до кінця файлу.

11. Function Eoln [(var F: Text)]: Boolean;

Перевіряє, чи поточна позиція файлу є кінцем рядка текстового файлу.

Eoln(F) повертає True, якщо поточна позиція файлу - наприкінці рядка чи файла; інакше Eoln (F) повертає False.

12. Procedure Read(F, V1 [, V2, ..., Vn]);

{Типізовані та нетипізовані файли}

Procedure Read([var F: Text;] V1 [, V2, ..., Vn]);

{Текстові файли}

Для типізованих файлів процедура читає компонент файлу змінну. При кожному зчитуванні поточна позиція файлу просувається до наступного елемента.

Для текстових файлів читається одне або кілька значень одного або декількох змінних.

Зі змінними типу String Read зчитує всі символи аж до наступної мітки кінця рядка (але не включаючи її) або до тих пір, поки функція Eof(F) не прийме значення True. Змінній присвоюється символьний рядок, що вийшов у результаті.

У разі змінної цілого або речового типу процедура очікує надходження послідовності символів, що утворюють число за правилами синтаксису Pascal. Зчитування припиняється при виявленні першого пробілу, символу табуляції або мітки кінця рядка або якщо функція Eof(F) приймає значення True. Якщо числовий рядок не відповідає очікуваному формату, відбувається помилка введення-виведення.

13. Procedure Readln([var F: Text;] V1 [, V2..., Vn]);

Є розширенням процедури Read та визначено для текстових файлів. Зчитує рядок символів у файлі, включаючи маркер кінця рядка, і переходить до наступного рядка. Виклик функції Readln(F) без параметрів призводить до переміщення поточної позиції файлу на початок наступного рядка, якщо вона є, інакше відбувається перехід до кінця файлу.

14. Function SeekEof [(var F: Text)]: Boolean;

Повертає ознаку кінця файлу і може використовуватись лише для відкритих текстових файлів. Зазвичай застосовується для зчитування числових значень текстових файлів.

15. Function SeekEoln [(var F: Text)]: Boolean;

Повертає ознаку кінця рядка у файлі та може використовуватись лише для відкритих текстових файлів. Зазвичай застосовується для зчитування числових значень текстових файлів.

16. Procedure Write([var F: Text;] P1 [, P2, ..., Pn]);

{Текстові файли}

Записує одну або більше величин текстового файлу.

Кожен параметр запису повинен мати тип Char, один з цілих типів (Byte, Shortlnt, Word, Longint, Cardinal), один з типів з плаваючою комою (Single, Real, Double, Extended, Currency), один з рядкових типів (PChar, AisiString , ShortString), або з логічних типів (Boolean, Bool).

Procedure Write(F, V1,..., Vn);

{Типізовані файли}

Записує змінну компонент файлу. Змінні VI ...., Vn повинні бути того ж типу, що і елементи файлу. При кожному записі змінної поточна позиція у файлі переміщується до наступного елемента.

17. Procedure Writeln([var F: Text;] [P1, P2, ..., Pn]);

{Текстові файли}

Виконує операцію Write, потім поміщає мітку кінця рядка файл.

Виклик Writeln(F) без параметрів записує у файл маркер кінця рядка. Файл має бути відкритим для виведення.

2. Модулі. Види модулів

Модуль (1Ж1Т) Pascal - це особливим чином оформлена бібліотека підпрограм. Модуль, на відміну програми, може бути запущений виконання самостійно, може лише брати участь у побудові програм та інших модулів. Модулі дозволяють створювати особисті бібліотеки процедур та функцій та будувати програми практично будь-якого розміру.

Модуль Pascal є окремо зберігається і незалежно компилируемую програмну одиницю. У випадку модуль - це сукупність програмних ресурсів, призначених для використання іншими програмами. Під програмними ресурсами розуміються будь-які елементи Pascal: константи, типи, змінні, підпрограми. Модуль сам по собі не є програмою, що його виконує, його елементи використовуються іншими програмними одиницями.

Всі програмні елементи модуля можна розбити на дві частини:

1) програмні елементи, призначені для використання іншими програмами або модулями, такі елементи називають видимими поза модулем;

2) програмні елементи, необхідних лише роботи самого модуля, їх називають невидимими (чи прихованими).

Відповідно до цього модуль, крім заголовка, містить три основні частини, званими інтерфейсною, здійсненною та ініціалізованою.

Загалом модуль має таку структуру:

unit <ім'я модуля>; {заголовок модуля}

інтерфейс

{Опис видимих ​​програмних елементів модуля}

реалізація

{опис прихованих програмних елементів модуля}

починати

{оператори ініціалізації елементів модуля}

end.

В окремому випадку модуль може не містити частини реалізації та частини ініціалізації, тоді структура модуля буде такою:

unit <ім'я модуля>; {заголовок модуля}

інтерфейс

{Опис видимих ​​програмних елементів модуля}

реалізація

end.

Використання у модулях процедур та функцій має свої особливості. Заголовок підпрограми містить усі відомості, необхідні для виклику: ім'я, перелік і тип параметрів, тип результату для функцій. Ця інформація має бути доступна для інших програм та модулів. З іншого боку, текст підпрограми, що реалізує її алгоритм, іншими програмами та модулями не може бути використаний. Тому заголовки процедур і функцій поміщають в інтерфейсну частину модуля, а текст - частину реалізації.

Інтерфейсна частина модуля містить лише видимі (доступні для інших програм та модулів) заголовки процедур та функцій (без службового слова forward). Повний текст процедури чи функції поміщають у частину реалізації, причому заголовок може містити списку формальних параметрів.

Вихідний текст модуля має бути відкомпільований за допомогою директиви Make підменю Compile та записаний на диск. Результатом компіляції модуля є файл із розширенням. TPU (Turbo Pascal Unit). Основне ім'я модуля береться із заголовка модуля.

Для підключення модуля до програми необхідно вказати його ім'я у розділі опису модулів, наприклад:

uses Crt, Graph;

У тому випадку, якщо імена змінних в інтерфейсній частині модуля та у програмі, що використовує цей модуль, збігаються, звернення відбуватиметься до змінної, описаної в програмі. Для звернення до змінної, описаної в модулі, необхідно застосувати складове ім'я, що складається з імені модуля та імені змінної, розділених точкою. Використання складових імен застосовується не тільки до імен змінних, а до всіх імен, описаних в інтерфейсній частині модуля.

Рекурсивне використання модулів заборонено.

Якщо в модулі є розділ ініціалізації, то оператори цього розділу будуть виконані перед початком виконання програми, в якій використовується цей модуль.

Перерахуємо, які види модулів бувають.

1. Модуль SYSTEM.

Модуль SYSTEM реалізує підтримуючі підпрограми нижнього рівня для всіх вбудованих засобів, таких як введення-виведення, робота з рядками, операції з плаваючою точкою та динамічний розподіл пам'яті.

Модуль SYSTEM містить усі стандартні та вбудовані процедури та функції Pascal. Будь-яка підпрограма Pascal, яка не є частиною стандартного Pascal і не знаходиться в жодному іншому модулі, міститься в модулі System. Цей модуль автоматично використовується у всіх програмах, і його не потрібно вказувати в операторі uses.

2. Модуль DOS.

Модуль Dos реалізує численні процедури та функції Pascal, які еквівалентні найчастіше використовуваним викликам DOS, як, наприклад, GetTime, SetTime, DiskSize і таке інше.

3. Модуль CRT.

Модуль CRT реалізує ряд потужних програм, що надають повну можливість керування засобами комп'ютера PC, такими як керування режимом екрана, розширені коди клавіатури, кольори, вікна та звукові сигнали. Модуль CRT може використовуватися лише у програмах, що працюють на персональних комп'ютерах IBM PC, PC AT, PS/2 фірми IBM та повністю сумісних із ними.

Однією з основних переваг використання модуля CRT є велика швидкість та гнучкість при виконанні операцій роботи з екраном. Програми, що не працюють з модулем CRT, виводять на екран інформацію за допомогою операційної системи DOS, що пов'язано з додатковими непродуктивними витратами. При використанні модуля CRT інформація, що виводиться, надсилається безпосередньо в базову систему вводу-виводу (BIOS) або для ще більш швидких операцій безпосередньо в відеопам'ять.

4. Модуль GRAPH.

За допомогою процедур і функцій, що входять до цього модуля, можна створювати різні графічні зображення на екрані.

5. Модуль OVERLAY.

Модуль OVERLAY дає змогу зменшити вимоги до пам'яті програми DOS реального режиму. Фактично можна писати програми, що перевищують загальний обсяг доступної пам'яті, оскільки у кожний момент у пам'яті буде лише частина програми.

Лекція № 7. Динамічна пам'ять

1. Посилальний тип даних. Динамічна пам'ять. Динамічні змінні

Статичною змінною (статично розміщеною) називається описана явним чином у програмі змінна, звернення до неї здійснюється на ім'я. Місце пам'яті розміщувати статичних змінних визначається при компіляції програми. На відміну від таких статичних змінних програм, написаних мовою Pascal, можуть бути створені динамічні змінні. Основна властивість динамічних змінних у тому, що вони створюються, і пам'ять їм виділяється під час виконання програми.

Розміщуються динамічні змінні динамічної області пам'яті (heap-области). Динамічна змінна явно не вказується в описах змінних, і до неї не можна звернутися по імені. Доступ до таких змінних здійснюється за допомогою покажчиків та посилань.

Посилальний тип (покажчик) визначає безліч значень, які вказують на динамічні змінні певного типу, що називається базовим типом. Змінна посилання типу містить адресу динамічної змінної в пам'яті. Якщо базовий тип є ще не описаним ідентифікатором, то він повинен бути описаний у тій самій частині опису типів, що і тип-покажчик.

Зарезервоване слово nil означає константу зі значенням покажчика, яка ні на що не вказує.

Наведемо приклад опису динамічних змінних.

var p1, p2 : ^real;

p3, p4: ^integer;

2. Робота з динамічною пам'яттю. Нетипізовані покажчики

Процедури та функції роботи з динамічною пам'яттю

1. Процедура New (var p: Pointer).

Виділяє місце в динамічній області пам'яті для розміщення динамічної змінної рЛ, та її адреса присвоює вказівнику нар.

2. Процедура Dispose (varp: Pointer).

Звільняє ділянку пам'яті, виділений розміщення динамічної змінної процедурою New, і значення покажчика р стає невизначеним.

3. Процедура GetMem (varp: Pointer; size: Word).

Виділяє ділянку пам'яті в heap-області, надає адресу його початку покажчику р, розмір ділянки в байтах визначається параметром size.

4. Процедура FreeMem (var p: Pointer; size: Word).

Звільняє ділянку пам'яті, адресу початку якого визначено покажчиком р, а розмір параметром size. Значення покажчика р стає невизначеним.

5. Процедура Mark(var p: Pointer)

Записує в покажчик адресу початку ділянки вільної динамічної пам'яті на момент її виклику.

6. Процедура Release (var p: Pointer)

Звільняє ділянку динамічної пам'яті, починаючи з адреси, записаної в покажчик р процедурою Mark, тобто очищає ту динамічну пам'ять, яка була зайнята після виклику процедури Mark.

7. Функція MaxAvaikLongint

Повертає довжину в байтах найдовшої вільної ділянки динамічної пам'яті.

8. Функція MemAvaikLongint

Повертає повний обсяг вільної динамічної пам'яті у байтах.

9. Допоміжна функція SizeOf(X):Word

Повертає обсяг у байтах, який займає X, причому X може бути або ім'ям змінної будь-якого типу, або ім'ям типу.

Вбудований тип Pointer позначає нетипізований покажчик, тобто покажчик, який не вказує на якийсь певний тип. Змінні типу Pointer можуть бути розіменовані: вказівка ​​символу ^ після такої змінної викликає появу помилки.

Як і значення, що позначається словом nil, значення типу Pointer сумісні з іншими типами покажчиків.

ЛЕКЦІЯ № 8. Абстрактні структури даних

1. Абстрактні структури даних

Структуровані типи даних, такі як масиви, множини, записи, є статичні структури, оскільки їх розміри незмінні протягом усього часу виконання програми.

Часто потрібно, щоб структури даних змінювали свої розміри під час вирішення завдання. Такі структури даних називають динамічними. До них належать стеки, черги, списки, дерева та ін.

Опис динамічних структур за допомогою масивів, записів та файлів призводить до неекономного використання пам'яті ЕОМ та збільшує час вирішення завдань.

Кожна компонента будь-якої динамічної структури є записом, що містить, принаймні, два поля: одне поле типу "покажчик", а друге - для розміщення даних. У випадку запис може містити не один, а кілька покажчиків і кілька полів даних. Поле даних може бути змінною, масивом, множиною або записом.

Якщо вказівна частина містить адресу одного елемента списку, то список називається односпрямованим (або однозв'язним). Якщо він містить дві компоненти, то двозв'язним. Над списками можна проводити різні операції, наприклад:

1) додавання елемента до списку;

2) видалення елемента зі списку із заданим ключем;

3) пошук елемента із заданим значенням ключового поля;

4) сортування елементів списку;

5) розподіл списку на два і більше списків;

6) об'єднання двох і більше списків до одного;

7) інші операції.

Однак, як правило, потреби у всіх операціях при вирішенні різних завдань не виникає. Тому, залежно від основних операцій, які необхідно застосувати, існують різні види списків. Найбільш популярні з них – це стек та черга.

2. Стеки

Стеком називається динамічна структура даних, додавання компоненти в яку і виняток компоненти з якої виробляється з одного кінця, що називається вершиною стека. Стек працює за принципом LIFO (Last-In, First-Out) - "Той, що поступив останнім, обслуговується першим".

Зазвичай над стеками виконується три операції:

1) початкове формування стека (запис першої компоненти);

2) додавання компоненти до стек;

3) вибірка компоненти (видалення).

Для формування стека та роботи з ним необхідно мати дві змінні типу "покажчик", перша з яких визначає вершину стека, а друга - допоміжна.

приклад. Скласти програму, яка формує стек, додає до нього довільну кількість компонент, а потім читає всі компоненти і виводить їх на екран дисплея. Як дані взяти рядок символів. Введення даних – з клавіатури, ознака кінця введення – рядок символів END.

Program STACK;

uses Crt;

тип

Alfa = String [10];

PComp = ^Comp;

Comp = Record

sD: Alfa;

pNext : PComp

end;

було

pTop: PComp;

sC: Alfa;

Procedure CreateStack(var pTop: PComp; var sC: Alfa);

починати

New (pTop);

pTop^.pNext := NIL;

pTop^.sD := sC;

end;

Procedure AddComp(var pTop: PComp; var sC: Alfa);

var pAux: PComp;

починати

NEW (pAux);

pAux^.pNext := pTop;

pTop: = pAux;

pTop^.sD := sC;

end;

Procedure DelComp (var pTop: PComp; var sC: ALFA);

починати

sC := pTop^.sD;

pTop := pTop^.pNext;

end;

починати

Clrscr;

writeln(' ВВЕДИ РЯДКУ ');

readln(sC);

CreateStack(pTop, sC);

повторювати

writeln(' ВВЕДИ РЯДКУ ');

readln(sC);

AddComp(pTop, sC);

until sC = 'END';

writeln('****** ВИСНОВОК РЕЗУЛbТАТІВ ******');

повторювати

DelComp(pTop, sC);

writeln(sC);

until pTop = NIL;

end.

3. Черги

Чергою називається динамічна структура даних, додавання компоненти в яку проводиться в один кінець, а вибірка здійснюється з іншого кінця. Черга працює за принципом FIFO (First-In, First-Out) - "Вступив першим, обслуговується першим".

Для формування черги та роботи з нею необхідно мати три змінні типу покажчик, перша з яких визначає початок черги, друга – кінець черги, третя – допоміжна.

приклад. Скласти програму, яка формує чергу, додає до неї довільну кількість компонент, а потім читає всі компоненти і виводить їх на екран дисплея. Як дані взяти рядок символів. Введення даних – з клавіатури, ознака кінця введення – рядок символів END.

Program QUEUE;

uses Crt;

тип

Alfa = String [10];

PComp = ^Comp;

Comp = record

sD: Alfa;

pNext: PComp;

end;

було

pBegin, pEnd: PComp;

sC: Alfa;

Procedure CreateQueue(var pBegin,pEnd:PComp; var sC:Alfa);

починати

New (pBegin);

pBegin^.pNext := NIL;

pBegin^.sD := sC;

pEnd: = pBegin;

end;

Procedure AddQueue(var pEnd: PComp; var sC: Alfa);

var pAux: PComp;

починати

New (pAux);

pAux^.pNext := NIL;

pEnd^.pNext := pAux;

pEnd: = pAux;

pEnd^.sD := sC;

end;

Procedure DelQueue(var pBegin: PComp; var sC: Alfa);

починати

sC := pBegin^.sD;

pBegin := pBegin^.pNext;

end;

починати

Clrscr;

writeln(' ВВЕДИ РЯДКУ ');

readln(sC);

CreateQueue(pBegin, pEnd, sC);

повторювати

writeln(' ВВЕДИ РЯДКУ ');

readln(sC);

AddQueue(pEnd, sC);

until sC = 'END';

writeln(' ***** ВИСНОВОК РЕЗУЛbТАТІВ *****');

повторювати

DelQueue(pBegin, sC);

writeln(sC);

until pBegin = NIL;

end.

ЛЕКЦІЯ № 9. Деревоподібні структури даних

1. Деревоподібні структури даних

Деревоподібною структурою даних називається кінцева множина елементів-вузлів, між якими існують відносини - зв'язок вихідного та породженого.

Якщо використовувати рекурсивне визначення, запропоноване М. Віртом, деревоподібна структура даних з базовим типом t - це або порожня структура, або вузол типу t, з яким пов'язане кінцеве безліч деревоподібних структур з базовим типом t, званих піддеревами.

Далі дамо визначення, що використовуються під час оперування деревоподібними структурами.

Якщо вузол знаходиться безпосередньо під вузлом х, то вузол називається безпосереднім нащадком вузла х, а x - безпосереднім предком вузла у, тобто, якщо вузол x знаходиться на i-му рівні, то відповідно вузол у знаходиться на (i + 1) - му рівні.

Максимальний рівень вузла дерева називається висотою чи глибиною дерева. Предка не має лише одного вузол дерева - його коріння.

Вузли дерева, які не мають нащадків, називаються термінальними вузлами (або листами дерева). Усі інші вузли називаються внутрішніми вузлами. Кількість безпосередніх нащадків вузла визначає ступінь цього вузла, а максимально можливий ступінь вузла у цьому дереві визначає ступінь дерева.

Предків і нащадків не можна поміняти місцями, т. е. зв'язок вихідного і породженого діє лише одному напрямі.

Якщо пройти від кореня дерева до певного конкретного вузла, то кількість гілок дерева, яке буде пройдено, називається довжиною шляху цього вузла. Якщо всі гілки (вузли) у дерева впорядковані, дерево називається впорядкованим.

Окремим випадком деревоподібних структур є бінарні дерева. Це дерева, у яких кожен нащадок має трохи більше двох нащадків, званих лівим і правим поддеревами. Таким чином, бінарне дерево - це деревоподібна структура, ступінь якої дорівнює двом.

Упорядкованість бінарного дерева визначається за таким правилом: кожному вузлу відповідає своє ключове поле, і для кожного вузла значення ключа більше всіх ключів у його лівому піддереві і найменше всіх ключів у його правому піддереві.

Дерево, ступінь якого більше двох, називається сильнорозгалуженим.

2. Операції над деревами

Далі будемо розглядати всі операції стосовно бінарних дерев.

I. Побудова дерева

Наведемо алгоритм побудови впорядкованого дерева.

1. Якщо дерево порожнє, дані переносяться в корінь дерева. Якщо дерево не порожнє, то здійснюється спуск по одній з його гілок таким чином, щоб упорядкованість дерева не порушувалася. В результаті новий вузол стає черговим аркушем дерева.

2. Щоб додати вузол у вже існуюче дерево, можна скористатися наведеним вище алгоритмом.

3. У разі видалення вузла з дерева слід бути уважним. Якщо видалений вузол є листом, або має тільки одного нащадка, то операція проста. Якщо ж вузол, що видаляється, має двох нащадків, то необхідно буде знайти вузол серед його нащадків, який можна буде поставити на його місце. Це необхідно через вимогу впорядкованості дерева.

Можна вчинити таким чином: поміняти вузол, що видаляється місцями з вузлом, що має найбільше значення ключа в лівому піддереві, або з вузлом, що має найменше значення ключа в правому піддереві, а потім видалити шуканий вузол як лист.

ІІ. Пошук вузла із заданим значенням ключового поля

При здійсненні цієї операції необхідно здійснити обхід деревини. Необхідно враховувати різні форми запису дерева: префіксний, інфіксний і постфіксний.

Виникає питання: як уявити вузли дерева, щоб було зручніше працювати із нею? Можна представляти дерево за допомогою масиву, де кожен вузол описується величиною комбінованого типу, яка має інформаційне поле символьного типу і два поля посилання типу. Але це не зовсім зручно, тому що дерева мають велику кількість вузлів, що заздалегідь не визначене. Тому краще при описі дерева використовувати динамічні змінні. Тоді кожен вузол представляється величиною одного типу, що містить опис заданої кількості інформаційних полів, а кількість відповідних полів має дорівнювати ступеню дерева. Логічно відсутність нащадків визначати ссьшкой nil. Тоді на мові Pascal опис бінарного дерева може виглядати так:

TYPE TreeLink = ^Tree;

Tree = record;

Inf: <тип даних>;

Left, Right: TreeLink;

Кінець.

3. Приклади реалізації операцій

1. Побудувати дерево з n вузлів мінімальної висоти, або ідеально збалансоване дерево (кількість вузлів лівого та правого піддерев'я такого дерева повинні відрізнятися не більше ніж на одиницю).

Рекурсивний алгоритм побудови:

1) перший вузол береться як корінь дерева.

2) тим самим способом будується ліве поддерево з nl вузлів.

3) тим самим способом будується праве поддерево з nr вузлів;

nr = n - nl - 1. Як інформаційне поле будемо брати номери вузлів, що вводяться з клавіатури. Рекурсивна функція, що реалізує цю побудову, буде виглядати так:

Function Tree (n: Byte): TreeLink;

Var t: TreeLink; nl, nr, x: Byte;

Починати

If n = 0 then Tree := nil

Ще

Починати

nl := n div 2;

nr = n - nl - 1;

writeln('Введіть номер вершини');

readln(x);

new(t);

t^.inf := x;

t^.left: = Tree (nl);

t^.right: = Tree (nr);

Tree: = t;

Кінець;

{Tree}

Кінець.

2. У бінарному упорядкованому дереві знайти вузол із заданим значенням ключового поля. Якщо такого елемента у дереві немає, то додати його до дерева.

Procedure Search(x: Byte; var t: TreeLink);

Починати

If t = nil then

Починати

New(t);

t^inf := x;

t^.left := nil;

t^.right := nil;

кінець

Else if x < t^.inf then

Search(x, t^.left)

Else if x > t^.inf then

Search(x, t^.right)

Ще

Починати

{обробка знайденого елемента}

...

Кінець;

Кінець.

3. Написати процедури обходу дерева у прямому, симетричному та зворотному порядку відповідно.

3.1. Procedure Preorder(t: TreeLink);

Починати

If t <> nil then

Починати

Writeln(t^.inf);

Preorder(t^.left);

Preorder(t^.right);

Кінець;

Кінець;

3.2. Procedure Inorder(t: TreeLink);

Починати

If t <> nil then

Починати

Inorder(t^.left);

Writeln(t^.inf);

Inorder(t^.right);

Кінець;

Кінець.

3.3. Procedure Postorder(t: TreeLink);

Починати

If t <> nil then

Починати

Postorder(t^.left);

Postorder(t^.right);

Writeln(t^.inf);

Кінець;

Кінець.

4. У бінарному упорядкованому дереві видалити вузол із заданим значенням ключового поля.

Опишемо рекурсивну процедуру, яка враховуватиме наявність необхідного елемента в дереві та кількість нащадків цього вузла. Якщо вузол, що видаляється, має двох нащадків, то він буде замінений найбільшим значенням ключа в його лівому піддереві, і тільки після цього він буде остаточно видалений.

Procedure Delete1(x: Byte; var t: TreeLink);

Var p: TreeLink;

Procedure Delete2(var q: TreeLink);

Починати

If q^.right <> nil then Delete2(q^.right)

Ще

Починати

p^.inf: = q^.inf;

p: = q;

q := q^.left;

Кінець;

Кінець;

Починати

If t = nil then

Writeln('шуканого елемента немає')

Else if x < t^.inf then

Delete1(x, t^.left)

Else if x > t^.inf then

Delete1(x, t^.right)

Ще

Починати

P: = t;

If p^.left = nil then

t := p^.right

Ще

If p^.right = nil then

t := p^.left

Ще

Delete2(p^.left);

Кінець;

Кінець.

ЛЕКЦІЯ №10. Графи

1. Поняття графа. Способи подання графа

Граф - пара G = (V,E), де V - безліч об'єктів довільної природи, званих вершинами, а Е - сімейство пар ei = (vil, vi2), vijOV, званих ребрами. У загальному випадку безліч V та (або) сімейство Е можуть містити нескінченну кількість елементів, але ми розглядатимемо лише кінцеві графи, тобто графи, у яких як V, так і Е кінцеві. Якщо порядок елементів, що входять до ei, має значення, то граф називається орієнтованим, скорочено – орграф, інакше – неорієнтованим. Ребра орграф називаються дугами. Надалі вважатимемо, що термін "граф", застосовуваний без уточнень (орієнтований чи неорієнтований), означає неорієнтований граф.

Якщо е = , то вершини v і називаються кінцями ребра. При цьому говорять, що ребро є суміжним (інцидентним) кожної з вершин v і в. Вершини v і також називаються суміжними (інцидентними). У випадку допускаються ребра виду е = ; такі ребра називаються петлями.

Ступінь вершини графа - це число ребер, інцидентних даній вершині, причому петлі враховуються двічі. Оскільки кожне ребро інцидентне двом вершинам, сума ступенів усіх вершин графа дорівнює подвоєній кількості ребер: Sum(deg(vi), i=1...|V|) = 2 * |E|.

Вага вершини - число (дійсне, ціле або раціональне), поставлене у відповідність даній вершині (інтерпретується як вартість, пропускна спроможність тощо). Вага, довжина ребра - число чи кілька чисел, які інтерпретуються як довжина, пропускна спроможність тощо.

Шляхом у графі (або маршрутом в орграфі) називається послідовність вершин і ребер (або дуг - в орграфі) виду v0, (v0,v1), v1..., (vn - 1,vn), vn. Число n називається довжиною шляху. Шлях без ребер, що повторюються, називається ланцюгом, без повторюваних вершин - простим ланцюгом. Шлях може бути замкнутим (v0 = vn). Замкнений шлях без ребер, що повторюються, називається циклом (або контуром в орграфі); без повторюваних вершин (крім першої та останньої) - простим циклом.

Граф називається зв'язковим, якщо існує шлях між будь-якими двома його вершинами, і незв'язним - інакше. Незв'язний граф складається з кількох зв'язкових компонентів (зв'язкових підграфів).

Існують різні способи подання графів. Розглянемо кожен із них окремо.

1. Матриця інцидентності.

Це прямокутна матриця розмірності nx щ, де n – кількість вершин, am – кількість ребер. Значення елементів матриці визначаються наступним чином: якщо ребро xi і вершина vj інцидентні, то значення відповідного елемента матриці дорівнює одиниці, інакше значення дорівнює нулю. Для орієнтованих графів матриця інцидентності будується за таким принципом: значення елемента дорівнює - 1, якщо ребро xi виходить з вершини vj, дорівнює 1, якщо ребро xi заходить у вершину vj, і Про в іншому випадку.

2. Матриця суміжності.

Це квадратна матриця розмірності nxn, де n – кількість вершин. Якщо вершини vi і vj суміжні, тобто якщо існує ребро, яке з'єднує, то відповідний елемент матриці дорівнює одиниці, в іншому випадку він дорівнює нулю. Правила побудови даної матриці для орієнтованого та неорієнтованого графів не відрізняються. Матриця суміжності компактніша, ніж матриця інцидентності. Слід зауважити, що ця матриця також сильно розріджена, проте у разі неорієнтованого графа вона є симетричною щодо головної діагоналі, тому можна зберігати не всю матрицю, а лише половину (трикутну матрицю).

3. Список суміжності (інцидентності).

Це структура даних, яка для кожної вершини графа зберігає список суміжних з нею вершин. Список являє собою масив покажчиків, i-ий елемент якого містить покажчик на список вершин, суміжних з i-ою вершиною.

Список суміжності ефективніший порівняно з матрицею суміжності, оскільки виключає зберігання нульових елементів.

4. Список списків.

Є деревоподібною структурою даних, в якій одна гілка містить списки вершин, суміжних для кожної з вершин графа, а друга гілка вказує на чергову вершину графа. Такий спосіб подання графа є найоптимальнішим.

2. Подання графа списком інцидентності. Алгоритм обходу графа у глибину

Для реалізації графа у вигляді списку інцидентності можна використовувати такий тип:

Type List = ^S;

S = record;

inf : Byte;

next : List;

end;

Тоді граф задається так:

Var Gr: array[1..n] of List;

Тепер звернемося до процедури обходу графа. Це допоміжний алгоритм, який дає змогу переглянути всі вершини графа, проаналізувати усі інформаційні поля. Якщо розглядати обхід графа в глибину, то є два типи алгоритмів: рекурсивний і нерекурсивний.

При рекурсивному алгоритмі обходу графа в глибину беремо довільну вершину і, відшукуємо довільну непроглянуту (нову) вершину v, суміжну із нею. Потім приймаємо вершину за ненову і відшукуємо будь-яку суміжну з нею нову вершину. Якщо ж у будь-якої вершини немає нових непроглянутих вершин, то вважаємо цю вершину використаної і повертаємося на рівень вище в ту вершину, з якої потрапили в нашу використану вершину. Обхід триває таким чином, поки в графі не залишиться нових непереглянутих вершин.

На мові Pascal процедура обходу в глибину буде виглядати так:

Procedure Obhod (gr: Graph; k: Byte);

Var g: Graph; l: List;

Починати

nov[k]: = false;

g: = gr;

While g^.inf <> k do

g := g^.next;

l := g^.smeg;

While l <> nil do begin

If nov[l^.inf] then Obhod(gr, l^.inf);

l := l^.next;

Кінець;

Кінець;

Примітка

У цій процедурі при описі типу Graph йшлося про опис графа списком списків. Масив nov[i] - спеціальний масив, i-ий елемент якого дорівнює True, якщо i-а вершина не переглянута, і False - інакше.

Також часто використовують нерекурсивний алгоритм обходу. І тут рекурсія замінюється на стек. Як тільки вершина переглянута, вона міститься в стек, а використаною вона стає, коли більше немає нових вершин, суміжних із нею.

3. Подання графа списком списків. Алгоритм обходу графа завширшки

Граф можна визначити за допомогою списку списків таким чином:

Type List = ^Tlist;

Tlist = record

inf : Byte;

next : List;

end;

Graph = ^TGpaph;

TGpaph = record

inf : Byte;

smeg: List;

next : Graph;

end;

При обході графа завширшки ми вибираємо довільну вершину і проглядаємо відразу всі вершини, суміжні з нею. Замість стеку використовується черга. Алгоритм обходу завширшки дуже зручний при знаходженні найкоротшого шляху у графі.

Наведемо процедуру обходу графа завширшки на псевдокоді:

Procedure Obhod2(v);

{величини spisok, nov - глобальні}

Починати

queue = O;

queue <= v;

nov [v] = False;

While queue <> O do

Починати

p<=queue;

For u in spisok(p) do

If nov[u] then

Починати

nov[u]: = False;

queue <= u;

Кінець;

Кінець;

Кінець;

ЛЕКЦІЯ №11. Об'єктний тип даних

1. Об'єктний тип у Pascal. Поняття об'єкта, його опис та використання

Історично першим підходом у програмуванні було процедурне програмування, інакше зване програмуванням знизу нагору. Спочатку створювалися загальні бібліотеки стандартних програм, які у різних галузях застосування ЕОМ. Потім на основі цих програм створювалися складніші програми для вирішення конкретних завдань.

Однак обчислювальна техніка постійно розвивалася, її почали застосовувати для вирішення різних завдань виробництва, економіки, у зв'язку з чим виникла необхідність обробки даних різного формату та вирішення нестандартних завдань (наприклад, нечислового характеру). Тому розробки мов програмування стали звертати увагу створення різних типів даних. Це сприяло появі таких складних типів даних, як комбіновані, множинні, рядкові, файлові та ін. Перш ніж вирішувати завдання, програміст проводив декомпозицію, тобто розбиття задачі на кілька підзавдань, для кожної з яких писав свій окремий модуль. Основна технологія програмування включала три етапи:

1) проектування зверху донизу;

2) модульне програмування;

3) структурне кодування.

Але починаючи з середини 60-х р. XX ст., Стали формуватися нові поняття та підходи, які лягли в основу технології об'єктно-орієнтованого програмування. У цьому підході здійснюється моделювання та опис реального світу лише на рівні понять конкретної предметної області, до якої належить вирішуване завдання.

Об'єктно-орієнтоване програмування є методом програмування, який дуже близько нагадує нашу поведінку. Воно є природною еволюцією ранніх нововведень у створенні мов програмування. Об'єктно-орієнтоване програмування є структурнішим, ніж усі попередні розробки, що стосуються структурного програмування. Воно також є більш модульним і абстрактнішим, ніж попередні спроби абстрагування даних і перенесення деталей програмування на внутрішній рівень. Об'єктно-орієнтована мова програмування характеризується трьома основними властивостями:

1) інкапсуляції. Комбінування записів з процедурами та функціями, що маніпулюють полями цих записів, формує новий тип даних - об'єкт;

2) Спадкування. Визначення об'єкта та його подальше використання для побудови ієрархії породжених об'єктів з можливістю для кожного породженого об'єкта, що відноситься до ієрархії, доступу до коду та даних всіх об'єктів, що породжують;

3) Поліморфізм. Привласнення дії одного імені, яке потім спільно використовується вниз і вгору по ієрархії об'єктів, причому кожен об'єкт ієрархії виконує цю дію способом саме йому відповідним.

Говорячи про об'єкт, ми вводимо до нового типу даних - об'єктний. Об'єктний тип є структурою, що з фіксованого числа компонентів. Кожен компонент є полем, що містить дані строго певного типу, або методом, що виконує операції над об'єктом. За аналогією з описом змінних опис поля вказує тип даних цього поля та ідентифікатор, що називає поле: за аналогією з описом процедури або функції опис методу вказує заголовок процедури, функції конструктора або деструктора.

Об'єктний тип може наслідувати компоненти іншого об'єктного типу. Якщо тип Т2 успадковує від типу Т1, тип Т2 є нащадком типу Т1, а сам тип Т1 є батьком типу Т2. Спадкування є транзитивним, тобто. якщо ТЗ успадковує від Т2, а Т2 успадковує від Т1, то ТЗ успадковує від Т1. Область (домен) об'єктного типу складається з нього самого та з усіх його спадкоємців.

Наступний вихідний код наводить приклад опису об'єктного типу, type

тип

Point = об'єкт

X, Y: integer;

end;

Rect = об'єкт

A, B: TPoint;

procedure Init(XA, YA, XB, YB: Integer);

procedure Copy(var R: TRectangle);

procedure Move(DX, DY: Integer);

procedure Grow(DX, DY: Integer);

procedure Intersect(var R: TRectangle);

procedure Union(var R: TRectangle);

функція Contains(P: Point): Boolean;

end;

StringPtr = ^String;

FieldPtr = ^TField;

TField = об'єкт

X, Y, Len: Integer;

Name: StringPtr;

constructor Copy (var F: TField);

constructor Init(FX, FY, FLen: Integer; FName: String);

destructor Done; virtual;

procedure Display; virtual;

procedure Edit; virtual;

function GetStr: String; virtual;

function PutStr(S: String): Boolean; virtual;

end;

StrFieldPtr = ^TStrField;

StrField = object(TField)

Value: PString;

constructor Init(FX, FY, FLen: Integer; FName: String);

destructor Done; virtual;

function GetStr: String; virtual;

function PutStr(S: String): Boolean;

virtual;

function Get: string;

procedure Put(S: String);

end;

NumFieldPtr = ^TNumField;

TNumField = object(TField)

приватний

Value, Min, Max: Longint;

громадськість

constructor Init(FX, FY, FLen: Integer; FName: String;

FMin, FMax: Longint);

function GetStr: String; virtual;

function PutStr(S: String): Boolean; virtual;

function Get: Longint;

функція Put (N: Longint);

end;

ZipFieldPtr = ^TZipField;

ZipField = object(TNumField)

function GetStr: String; virtual;

function PutStr(S: String): Boolean;

virtual;

end.

На відміну від інших типів, об'єктні типи можуть описуватися лише в розділі описів типів, що знаходиться на зовнішньому рівні області дії програми або модуля. Таким чином, об'єктні типи не можуть описуватися в розділі описів змінних або всередині процедури, функції або методу.

Тип компоненти файлового типу не може мати об'єктний тип або структурний тип, що містить компоненти об'єктного типу.

2. Спадкування

Процес, з допомогою якого один тип успадковує характеристики іншого типу, називається успадкуванням. Спадкоємець називається породженим (дочірнім) типом, а тип, якому успадковує дочірній тип, називається породжуючим (батьківським) типом.

Раніше відомі типи записів Pascal не можуть наслідувати. Однак Borland Pascal розширює мову Pascal для підтримки спадкування. Одним із цих розширень є нова категорія структури даних, пов'язана із записами, але значно потужніша. Типи даних у цій новій категорії визначаються з допомогою нового зарезервованого слова "object". Тип об'єкта може бути визначений як повний, самостійний тип у манері опису записів Pascal, але він може визначатися і як нащадок існуючого типу об'єкта шляхом поміщення породжуючого (батьківського) типу у дужки після зарезервованого слова "object".

3. Створення екземплярів об'єктів

Примірник об'єкта створюється за допомогою опису змінної або константи об'єктного типу або шляхом застосування стандартної процедури New до змінної типу "покажчик на об'єктний тип". Результуючий об'єкт називається екземпляром об'єктного типу;

було

F: TField;

Z: TZipField;

FP: PField;

ZP: PZipField;

З урахуванням цих описів змінних F є екземпляром TField, a Z - екземпляром TZipField. Аналогічно, після застосування New до FP та ZP FP вказуватиме на екземпляр TField, a ZP - на екземпляр TZipField.

Якщо об'єктний тип містить віртуальні методи, екземпляри цього об'єктного типу повинні ініціалізуватися за допомогою виклику конструктора перед викликом будь-якого віртуального методу.

Нижче наведено приклад:

було

S: StrField;

egin

S.Init (1, 1, 25, 'Перше ім'я');

S.Put ("Володимир");

S.Display;

...

S.Done;

end.

Якщо S.Init не викликався, виклик S.Display призведе до невдалого завершення цього прикладу.

Привласнення екземпляра об'єктного типу не передбачає ініціалізації екземпляра. Об'єкт ініціалізується кодом, що генерується компілятором, який виконується між викликом конструктора і моментом, коли виконання фактично досягає першого оператора в блоці коду конструктора.

Якщо екземпляр об'єкта не ініціалізується і перевірка діапазону включена (директивою {SR+}), перший виклик віртуального методу екземпляра об'єкта дає помилку етапу виконання. Якщо перевірка діапазону вимкнена (директивою {SR-}), перший виклик віртуального методу неініціалізованого об'єкта може призвести до непередбачуваної поведінки.

Правило обов'язкової ініціалізації застосовується також до екземплярів, що є компонентами структурних типів. Наприклад:

було

Comment: array [1..5] of TStrField;

I: integer;

починати

for I := 1 to 5 do

Comment [I]. Init (1, I + 10, 40, 'перше_ім'я');

.

.

.

for I := 1 to 5 do Comment [I].Done;

end;

Для динамічних екземплярів ініціалізація зазвичай пов'язана з розміщенням, а очищення - з видаленням, що досягається завдяки розширеному синтаксису стандартних процедур New і Dispose. Наприклад:

було

SP: StrFieldPtr;

починати

New (SP, Init (1, 1, 25, 'перше_ім'я');

SP^.Put ("Володимир");

SP^.Display;

.

.

.

Dispose (SP, Done);

end.

Вказівник на об'єктний тип є сумісним за присвоєнням з вказівником на будь-який батьківський об'єктний тип, тому під час виконання програми вказівник на об'єктний тип може вказувати на екземпляр цього типу або екземпляр будь-якого дочірнього типу.

Наприклад, покажчик типу ZipFieldPtr може присвоюватися покажчикам типу PZipField, PNumField і PField, а під час виконання програми покажчик типу PField може або мати значення nil, або вказувати на екземпляр TField, TNumField, або TZipField, або на будь-який екземпляр .

Ці правила сумісності покажчиків із присвоєння застосовні також параметрам - змінним об'єктного типу. Наприклад, методу TField.Сміттю можуть бути передані екземпляри типів TField, TStrField, TNumField, TZipField або будь-які інші екземпляри дочірнього від TField типу.

4. Компоненти та сфера дії

Область дії ідентифікатора компоненти тягнеться за межі об'єктного типу. Більш того, область дії ідентифікатора компонента простягається крізь блоки процедур, функцій, конструкторів та деструкторів, які реалізують методи об'єктного типу та його спадкоємців. Виходячи з цих міркувань написання ідентифікатора компоненти має бути унікальним всередині об'єктного типу і всередині всіх його спадкоємців, а також усередині його методів.

Область дії ідентифікатора компонента, описаного частини private описи типу, обмежується модулем (програмою), яка містить опис об'єктного типу. Іншими словами, приватні (private) компоненти-ідентифікатори діють як звичайні загальнодоступні ідентифікатори в рамках модуля, що містить опис об'єктного типу, а поза модулем будь-які приватні компоненти та ідентифікатори невідомі та недоступні. Помістивши в один модуль зв'язані типи об'єктів, можна зробити так, що ці об'єкти можуть звертатися до приватних компонентів один одного, і ці приватні компоненти будуть невідомі іншим модулям.

В описі об'єктного типу заголовок методу може задавати параметри об'єктного типу, що описується, навіть якщо опис ще неповний.

лекція № 12. Методи

1. Методи

Опис методу всередині об'єктного типу відповідає випереджальному опису методу (forward). Таким чином, десь після опису об'єктного типу, але всередині тієї ж області дії, що і область дії опису об'єктного типу, метод повинен реалізуватися шляхом визначення його опису.

Для процедурних і функціональних методів визначальний опис має форму звичайного опису процедури або функції з винятком, що в цьому випадку ідентифікатор процедури або функції розглядається як ідентифікатор методу.

Для методів конструкторів та деструкторів визначальний опис набуває форми опису процедурного методу з тим винятком, що зарезервоване слово procedure замінюється зарезервованим словом constructor або destructor.

Визначальний опис методу може повторювати (але не обов'язково) список формальних параметрів заголовка методу об'єктного типу. У цьому випадку заголовок методу повинен точно повторювати заголовок в об'єктному типі в порядку, типах та іменах параметрів і в типі результату, що повертається функцією, якщо метод є функцією.

У визначальному описі методу завжди є неявний параметр з ідентифікатором Self, що відповідає формальному параметру-змінної, що володіє об'єктним типом. Всередині блоку методу Self представляє екземпляр, компонент методу якого було вказано для активізації методу. Таким чином, будь-які зміни значень полів Self відбиваються на екземплярі.

Область дії ідентифікатора компонента об'єктного типу поширюється на блоки процедур, функцій, конструкторів та деструкторів, які реалізують методи даного об'єктного типу. Ефект виходить той самий, ніби на початок блоку методу був вставлений оператор with у наступній формі:

with Self do

починати

...

end;

Виходячи з цих міркувань написання ідентифікаторів компонентів, формальних параметрів методу, Self та будь-якого ідентифікатора, введеного у виконувану частину методу, має бути унікальним.

Якщо потрібний унікальний ідентифікатор методу, використовується уточнений ідентифікатор методу. Він складається з ідентифікатора типу об'єкта, за яким слідують точка та ідентифікатор методу. Як і будь-якому іншому ідентифікатору, ідентифікатору уточненого методу, якщо потрібно, можуть передувати ідентифікатор пакета та точка.

Віртуальні методи

За замовчуванням методи статичні, однак вони можуть, за винятком конструкторів, бути віртуальними (за допомогою включення директиви virtual в опис методу). Компілятор дозволяє посилання на виклики статичних методів під час компіляції, тоді як виклики віртуальних методів дозволяються під час виконання. Це іноді називають пізнім зв'язуванням.

Якщо об'єктний тип оголошує або успадковує будь-який віртуальний метод, змінні цього типу повинні бути ініціалізовані за допомогою виклику конструктора перед викликом будь-якого віртуального методу. Таким чином, об'єктний тип, який описує або успадковує віртуальний метод, повинен також описувати або успадковувати принаймні один метод-конструктор.

Об'єктний тип може перевизначати будь-який із методів, які він успадковує від своїх батьків. Якщо опис методу в нащадку вказує той самий ідентифікатор методу, як і опис методу в батьку, то опис у нащадку перевизначає опис у батьку. Область дії перевизначального методу розширюється до сфери дії нащадка, в якому цей метод був введений, і залишатиметься такою, доки ідентифікатор методу не буде перевизначено знову.

Перевизначення статичного методу залежить від зміни заголовка методу. На противагу цьому, перевизначення віртуального методу має зберігати порядок, типи та імена параметрів, а також типи результатів функцій, якщо такі є. Більше того, перевизначення знову ж таки має включати директиву virtual.

Динамічні методи

Borland Pascal підтримує додаткові методи з пізнім зв'язуванням, які називають динамічними методами. Динамічні методи відрізняються від віртуальних лише характером їхньої диспетчеризації на етапі виконання. У всіх інших відносинах динамічні методи вважаються еквівалентними віртуальним.

Опис динамічного методу еквівалентно опису віртуального методу, але опис динамічного методу повинен включати індекс динамічного методу, який вказується безпосередньо за ключовим словом virtual. Індекс динамічного методу повинен бути цілою константою в діапазоні від 1 до 656535 і повинен бути унікальним серед індексів інших динамічних методів, що містяться в об'єктному типі або його предках. Наприклад:

procedure FileOpen(var Msg: TMessage); virtual 100;

Перевизначення динамічного методу має відповідати порядку, типу та імен параметрів і точно відповідати типу результату функції методу, що породжує. Перевизначення також має включати директиву virtual, за якою слідує той же індекс динамічного методу, який був заданий в об'єктному типі предка.

2. Конструктори та деструктори

Конструктори та деструктори є спеціалізованими формами методів. Конструктори і деструктори, що використовуються у зв'язку з розширеним синтаксисом стандартних процедур New і Dispose, мають здатність розміщення і видалення динамічних об'єктів. Крім того, конструктори мають можливість виконати необхідну ініціалізацію об'єктів, що містять віртуальні методи. Як і всі інші методи, конструктори та деструктори можуть успадковуватися, а об'єкти можуть містити будь-яку кількість конструкторів та деструкторів.

Конструктори використовуються для ініціалізації новостворених об'єктів. Зазвичай ініціалізація ґрунтується на значеннях, що передаються конструктору як параметри. Конструктор може бути віртуальним, оскільки механізм диспетчеризації віртуального методу залежить від конструктора, який першим зробив ініціалізацію об'єкта.

Наведемо кілька прикладів конструкторів:

constructor Field.Copy(var F: Field);

починати

Self: = F;

end;

constructor Field.Init(FX, FY, FLen: integer; FName: string);

починати

X: = FX;

Y := FY;

GetMem (Name, Length (FName) + 1);

Name^ := FName;

end;

constructor TStrField.Init(FX, FY, FLen: integer; FName: string);

починати

inherited Init(FX, FY, FLen, FName);

Field.Init(FX, FY, FLen, FName);

GetMem (Value, Len);

Value^ := '';

end;

Головною дією конструктора породженого (дочірнього) типу, такого, як зазначений вище TStr Field. Init, майже завжди виклик відповідного конструктора його безпосереднього батька для ініціалізації успадкованих полів об'єкта. Після виконання цієї процедури конструктор ініціалізує поля об'єкта, що належать лише породженому типу.

Деструктори є протилежностями конструкторів та використовуються для очищення об'єктів після їх використання. Зазвичай очищення полягає у видаленні всіх полів покажчиків в об'єкті.

Примітка

Деструктор може бути віртуальним і часто є таким. Деструктор рідко має параметри.

Наведемо кілька прикладів деструкторів:

destructor Field.Done;

починати

FreeMem(Name, Length (Name^) + 1);

end;

destructor StrField.Done;

починати

FreeMem (Value, Len);

Field.Done;

end;

Деструктор дочірнього типу, такий як зазначений вище TStrField. Done зазвичай спочатку видаляє введені в породженому типі поля покажчиків, а потім як остання дія викликає відповідний збирач-деструктор безпосереднього батька для видалення успадкованих полів покажчиків об'єкта.

3. Деструктори

Borland Pascal надає спеціальний тип методу, що називається збирачем сміття (або деструктором) для очищення та видалення динамічно розміщеного об'єкта. Деструктор об'єднує крок видалення об'єкта з будь-якими іншими діями чи завданнями, необхідними даного типу об'єкта. Для єдиного типу об'єкта можна визначити кілька деструкторів.

Деструктор визначається спільно з іншими методами об'єкта у визначенні типу об'єкта:

tyрe

TEmployee = об'єкт

Name: string[25];

Title: string[25];

Rate: Real;

constructor Init(AName, ATitle: String; ARate: Real);

destructor Done; virtual;

function GetName: String;

function GetTitle: String;

function GetRate: Rate; virtual;

функція GetPayAmount: Real; virtual;

end;

Деструктори можна успадковувати, і вони можуть бути статичними, або віртуальними. Оскільки різні програми завершення зазвичай потребують різних типів об'єктів, зазвичай рекомендується, щоб деструктори завжди були віртуальними, завдяки чому для кожного типу об'єкта буде виконаний правильний деструктор.

Зарезервоване слово destructor не потрібно вказувати для кожного методу очищення, навіть якщо визначення типу об'єкта містить віртуальні методи. Деструктори насправді працюють лише з динамічно розміщеними об'єктами.

При очищенні динамічно розміщеного об'єкта деструктор здійснює спеціальні функції: він гарантує, що в області пам'яті, що динамічно розподіляється, завжди буде звільнятися правильне число байтів. Не може бути жодних побоювань щодо використання деструктора стосовно статично розміщених об'єктів; Власне, не передаючи типу об'єкта деструктору, програміст позбавляє об'єкт цього типу повних переваг управління динамічної пам'яттю в Borland Pascal.

Деструктори насправді стають самими собою тоді, коли мають очищатися поліморфічні об'єкти і коли має звільнятися пам'ять, яку вони займають.

Поліморфічні об'єкти - це об'єкти, які присвоєно батьківському типу завдяки правилам сумісності розширених типів Borland Pascal. Примірник об'єкта типу THourly присвоєний змінної типу TEmployee, є прикладом поліморфічного об'єкта. Ці правила можуть бути застосовані до об'єктів; покажчик на THourly може вільно бути присвоєний покажчику на TEmployee, а об'єкт, що вказується цим покажчиком, знову ж таки буде поліморфічним об'єктом. Термін "поліморфічний" є відповідним, тому що код, що обробляє об'єкт, "не знає" точно під час компіляції, який тип об'єкта йому доведеться зрештою обробити. Єдине, що він знає, це те, що цей об'єкт належить ієрархії об'єктів, які є нащадками зазначеного типу об'єкта.

Вочевидь, що розміри типів об'єктів відрізняються. Тому коли настає час очищення розміщеного в динамічній пам'яті поліморфічного об'єкта, то як же Dispose дізнається, скільки байт динамічного простору потрібно звільняти? Під час компіляції з поліморфічного об'єкта не можна отримати жодної інформації щодо розміру об'єкта.

Деструктор дозволяє цю головоломку шляхом звернення до місця, де ця інформація записана, - у ТВМ змінних реалізацій. У кожному ТВМ типу об'єкта міститься розмір байтів даного типу об'єкта. Таблиця віртуальних методів будь-якого об'єкта доступна за допомогою прихованого параметра Self, що посилається методу виклику методу. Деструктор є лише різновидом методу, і тому, коли об'єкт викликає його, деструктор отримує копію Self через стек. Таким чином, якщо об'єкт є поліморфічним під час компіляції, він ніколи не буде поліморфічним під час виконання завдяки пізньому зв'язуванню.

Для виконання цього звільнення пам'яті при пізньому зв'язуванні деструктор потрібно викликати як частину розширеного синтаксису процедури Dispose:

Dispose(P, Done);

(Виклик деструктора поза процедурою Dispose взагалі виконує ніякого звільнення пам'яті.) Тут відбувається насправді те, що збирач сміття об'єкта, який вказує Р, виконується як стандартний метод. Однак, як тільки остання дія виконана, деструктор шукає розмір реалізації свого типу в ТВМ і пересилає розмір процедури Dispose. Процедура Dispose завершує процес шляхом видалення правильного числа байт простору динамічної пам'яті, яке (простір) раніше ставилося до Р^. Число байт, що звільняються, буде правильним незалежно від того, чи вказував Р на екземпляр типу TSalaried, чи він вказував на один з дочірніх типів типу TSalaried, наприклад на TCommissioned.

Зауважте, що сам собою метод деструктора може бути порожній і виконувати тільки цю функцію:

destructor AnObject.Done;

починати

end;

Те, що робиться корисним у цьому деструкторі, не є надбанням його тіла, проте при цьому компілятором генерується код епілогу у відповідь на зарезервоване слово destructor. Це нагадує модуль, який нічого не експортує, але здійснює деякі невидимі дії за рахунок виконання своєї секції ініціалізації перед стартом програми. Усі дії відбуваються "за лаштунками".

4. Віртуальні методи

Метод стає віртуальним, якщо його оголошенням у типі об'єкта стоїть нове зарезервоване слово virtual. Якщо оголошується метод у батьківському типі як virtual, всі методи з аналогічними іменами в дочірніх типах також повинні оголошуватися віртуальними щоб уникнути помилки компілятора.

Нижче наведено об'єкти з прикладу платіжної відомості, належним чином віртуалізовані:

tyрe

PEmрloyee = ^TEmployee;

TEmployee = об'єкт

Name, Title: string[25];

Rate: Real;

конструктор Init (AName, ATitle: String; ARate: Real);

function GetPayAmount: Real; virtual;

function GetName: String;

function GetTitle: String;

function GetRate: Real;

рrocedure Show; virtual;

end;

PHourly = ^THourly;

THourly = object(TEmployee);

Time: Integer;

конструктор Init (AName, ATitle: String; ARate: Real; Time: Integer);

function GetPayAmount: Real; virtual;

function GetTime: Integer;

end;

PSalaried = ^TSalaried;

TSalaried = object(TEmployee);

function GetPayAmount: Real; virtual;

end;

PCommissioned = ^TCommissioned;

TCommissioned = object(Salaried);

Commission: Real;

SalesAmount : Real;

constructor Init (AName, ATitle: String; ARate,

ACommission, ASalesAmount: Real);

function GetPayAmount: Real; virtual;

end;

Конструктор є спеціальним типом процедури, яка виконує деяку роботу для механізму віртуальних методів. Більше того, конструктор повинен викликатись перед викликом будь-якого віртуального методу. Виклик віртуального методу без попереднього виклику конструктора може призвести до блокування системи, а компілятор немає способу перевірити порядок виклику методів.

Кожен тип об'єкта, має віртуальні способи, повинен мати конструктор.

попередження

Конструктор повинен викликатись перед викликом будь-якого іншого віртуального методу. Виклик віртуального методу без попереднього звернення до конструктора може викликати блокування системи, і компілятор зможе перевірити порядок, у якому викликаються методи.

Примітка

Для конструкторів об'єкта пропонується використовувати ідентифікатор Init.

Кожен окремий екземпляр об'єкта має ініціалізуватись окремим викликом конструктора. Недостатньо ініціалізувати один екземпляр об'єкта і потім надавати цей екземпляр іншим. Інші екземпляри, навіть якщо вони можуть містити правильні дані, не будуть ініціалізовані оператором присвоєння та заблокують систему за будь-яких викликів їх віртуальних методів. Наприклад:

було

FBee, GBee: Bee; {Створити два екземпляри Bee}

починати

FBee.Init(5, 9) { виклик конструктора для FBee }

GBee: = FBee; {Gbee неприпустимий! }

end;

Що саме створює конструктор? Кожен тип об'єкта містить щось таке, що називається таблицею віртуального методу (ТВМ) у сегменті даних. ТВМ містить розмір типу об'єкта та для кожного віртуального методу покажчик на код, що виконує цей метод. Конструктор встановлює зв'язок між реалізацією об'єкта і ТВМ типу об'єкта, що викликає його.

Важливо пам'ятати, що є лише одна ТВМ кожного типу об'єкта. Окремі екземпляри типу об'єкта (тобто змінні цього типу) містять лише з'єднання з ТВМ, але не саму ТВМ. Конструктор встановлює значення цього з'єднання ТВМ. Саме завдяки цьому ніде не можна запустити виконання перед викликом конструктора.

5. Поля даних об'єкта та формальні параметри методу

Висновок з того факту, що методи та їх об'єкти поділяють загальну сферу дії, є те, що формальні параметри методу не можуть бути ідентичними будь-якому з полів даних об'єкта. Це не якесь нове обмеження, що накладається об'єктно-орієнтованим програмуванням, а, швидше, тими самими старими правилами області дії, які Pascal мав завжди. Це те саме, що й заборона для формальних параметрів процедури бути ідентичним локальним змінним цієї процедури:

procedure CrunchIt(Crunchee: MyDataRec, Crunchby,

ErrorCode: integer);

було

A, B: char;

ErrorCode: integer;

починати

.

.

.

Локальні змінні процедури та її формальні параметри спільно використовують спільну сферу дії і тому не можуть бути ідентичними. Буде отримано повідомлення "Error 4: Duplicate identifier" (Помилка 4; Повторення ідентифікатора), якщо спробувати компілювати щось подібне, та сама помилка виникає при спробі привласнити формальному параметру методу імені поля об'єкта, якому даний метод належить.

Обставини дещо відрізняються, оскільки приміщення заголовка процедури всередину структури даних є натяком на нововведення в Turbo Pascal, але основні принципи дії Pascal не змінилися.

ЛЕКЦІЯ № 13. Сумісність типів об'єктів

1. Інкапсуляція

Об'єднання в об'єкті коду та даних називається інкапсуляцією. В принципі, можливо надати достатньо методів, завдяки яким користувач об'єкта ніколи не буде звертатися до полів об'єкта безпосередньо. Деякі інші об'єктно-орієнтовані мови, наприклад Smalltalk, вимагають обов'язкової інкапсуляції, проте Borland Pascal є вибір.

Наприклад, об'єкти TEmployee і THourly написані таким чином, що цілком виключена необхідність прямого звернення до їх внутрішніх полів даних:

тип

TEmployee = об'єкт

Name, Title: string[25];

Rate: Real;

procedure Init (AName, ATitle: string; ARate: Real);

function GetName: String;

function GetTitle: String;

function GetRate: Real;

function GetPayAmount: Real;

end;

THourly = object(TEmployee)

Time: Integer;

procedure Init(AName, ATitle: string; ARate:

Real, Atime: Integer);

function GetPayAmount: Real;

end;

Тут присутні лише чотири поля даних: Name, Title, Rate та Time. Методи GetName та GetTitle виводять прізвище працюючого та його посаду відповідно. Метод GetPayAmount використовує Rate, а у випадку працюючого THourly та Time для обчислення суми виплат працюючому. Тут уже немає потреби звертатися безпосередньо до цих полів даних.

Припустивши існування екземпляра AnHourly типу THourly, ми могли б використовувати набір методів для маніпулювання полями даних AnHourly, наприклад:

with AnHourly do

починати

Init (Александр Петров, Fork lift operator' 12.95, 62);

{Виводить на екран прізвище, посаду та суму виплат}

Шоу;

end;

Слід звернути увагу на те, що доступ до полів об'єкта здійснюється не інакше, як тільки за допомогою методів цього об'єкта.

2. Розширювані об'єкти

На жаль, стандартний Pascal не надає жодних можливостей для створення гнучких процедур, що дозволяють працювати з абсолютно різними типами даних. Об'єктно-орієнтоване програмування вирішує цю проблему за допомогою успадкування: якщо визначено породжений тип, то методи типу, що породжує, успадковуються, проте за бажання вони можуть перевизначатися. Для перевизначення успадкованого методу просто описується новий метод із тим самим ім'ям, як і успадкований метод, але з іншим тілом і (за потреби) з іншим безліччю параметрів.

Визначимо дочірній по відношенню до TEmployee тип, що представляє працівника, якому сплачується годинна ставка, у наступному прикладі:

сопзЬ

PayPeriods = 26; { періоди виплат }

OvertimeThreshold = 80; {на період виплати}

OvertimeFactor = 1.5; { Погодинний коефіцієнт }

тип

THourly = object(TEmployee)

Time: Integer;

procedure Init(AName, ATitle: string; ARate:

Real, Atime: Integer);

function GetPayAmount: Real;

end;

procedure THourly.Init(AName, ATitle: string;

ARate: Real, Atime: Integer);

починати

TEmployee.Init(AName, ATitle, ARate);

Time: = ATime;

end;

function THourly.GetPayAmount: Real;

було

Overtime: Integer;

починати

Overtime := Time - OvertimeThreshold;

if Overtime > 0 then

GetPayAmount := RoundPay(OvertimeThreshold * Rate +

Rate OverTime * OvertimeFactor * Rate)

ще

GetPayAmount := RoundPay(Time * Rate)

end;

Людина, якій платиться годинна ставка, є працюючою: вона володіє всім тим, що використовується для визначення об'єкта TEmployee (прізвищем, посадою, ставкою), і лише кількість одержуваних погодинником грошей залежить від того, скільки годин він відпрацював за період, що підлягає оплаті. Таким чином, для THourly потрібно ще й поле часу Time.

Так як THourly визначає нове поле Time, його ініціалізація вимагає нового методу Init, який ініціалізує час і спадкові поля. Замість безпосередньо привласнити значення успадкованим полям, таким як Name, Title і Rate, чому б не використовувати знову метод ініціалізації об'єкта TEmployee (ілюстрований першим оператором THourly Init).

Виклик методу, який перевизначається, не є найкращим стилем. Загалом можливо, що TEmployee.Init виконує важливу, проте приховану ініціалізацію.

Викликаючи метод, що перевизначається, необхідно бути впевненим у тому, що породжений тип об'єкта включає функціональність батька. Крім того, будь-яка зміна батьківського методу автоматично впливає на всі породжені.

Після виклику TEmployee.Init, THourly.Init може потім виконати свою власну ініціалізацію, яка в цьому випадку полягає лише у присвоєнні значення, переданого Atime.

Іншим прикладом методу, що перевизначається, є функція THourly.GetPayAmount, що обчислює суму виплат працюючому на погодинній ставці. Насправді кожен тип об'єкта TEmployee має свій метод GetPayAmount, тому що тип працюючого залежить від того, як проводиться розрахунок. Метод THourly.GetPayAmount повинен враховувати, скільки годин працював співробітник, чи були понаднормові роботи, який коефіцієнт збільшення за понаднормові роботи і т.д.

Метод TSдозволяє. GetPayAmount повинен лише ділити ставку працюючого на кількість виплат у кожному році (у нашому прикладі).

unit Workers;

інтерфейс

сопзЬ

PayPeriods = 26; {на рік}

OvertimeThreshold = 80; {за кожен період оплати}

OvertimeFactor =1.5; {збільшення проти звичайної оплати}

тип

TEmployee = об'єкт

Name, Title: string[25];

Rate: Real;

procedure Init (AName, ATitle: string; ARate: Real);

function GetName: String;

function GetTitle: String;

function GetRate: Real;

function GetPayAmount: Real;

end;

THourly = object(TEmployee)

Time: Integer;

procedure Init(AName, ATitle: string; ARate:

Real, Atime: Integer);

function GetPayAmount: Real;

function GetTime: Real;

end;

TSalaried = об'єкт(TEmployee)

function GetPayAmount: Real;

end;

TCommissioned = object(TSalaried)

Commission: Real;

SalesAmount : Real;

constructor Init (AName, ATitle: String; ARate,

ACommission, ASalesAmount: Real);

function GetPayAmount: Real;

end;

реалізація

function RoundPay(Wages: Real): Real;

{округлюємо суму виплат, щоб ігнорувати суми менше

грошової одиниці}

починати

RoundPay: = Trunc (Wages * 100) / 100;

.

.

.

TEmployee є вершиною нашої ієрархії об'єктів і містить перший метод GetPayAmount.

function TEmployee.GetPayAmount: Real;

починати

RunError(211); {Дати помилку етапу виконання}

end;

Може викликати здивування те, що метод дає помилку часу виконання. Якщо викликається Employee.GetPayAmount, то програма виникає помилка. Чому? Тому що TEmployee є вершиною нашої ієрархії об'єктів і не визначає реального робітника; отже, жоден з методів TEmployee не викликається певним чином, хоча вони можуть бути успадкованими. Усі наші працівники є або погодинниками, або мають оклади, або працюють на пільщині. Помилка на етапі виконання припиняє виконання програми та виводить 211, що відповідає повідомленню про помилку, пов'язану з викликом абстрактного методу (якщо програма помилково викликає TEmployee.GetPayAmount).

Нижче наводиться метод THourly.GetPayAmount, в якому враховуються такі речі, як понаднормова оплата, кількість відпрацьованих годинників і т.д.

function THourly.GetPayAMount: Real;

було

OverTime: Integer;

починати

Overtime := Time - OvertimeThreshold;

if Overtime > 0 then

GetPayAmount := RoundPay(OvertimeThreshold * Rate +

Rate OverTime * OvertimeFactor * Rate)

ще

GetPayAmount := RoundPay(Time * Rate)

end;

Метод TSalaried.GetPayAmount набагато простіше; у ньому ставка

ділиться на кількість виплат:

function TSalaried.GetPayAmount: Real;

починати

GetPayAmount := RoundPay(Rate / PayPeriods);

end;

Якщо глянути на метод TCommissioned.GetPayAmount, то буде видно, що він викликає TSalaried.GetPayAmount, обчислює комісійні та додає їх до величини, що повертається методом TSalaried. GetPayAmount.

function TСommissioned.GetPayAmount : Real;

починати

GetPayAmount := RoundPay(TSalaried.GetPayAmount +

Commission * SalesAmount);

end;

Важливе зауваження: хоча методи можуть бути перевизначені, поля даних не можуть перевизначатися. Після того, як було визначено поле даних в ієрархії об'єкта, ніякий дочірній тип не може визначити поле даних точно з таким же ім'ям.

3. Сумісність типів об'єктів

Спадкування дещо змінює правила сумісності типів в Borland Pascal. Крім того, породжений тип успадковує сумісність типів всіх своїх породжуючих типів.

Ця розширена сумісність типів набуває трьох форм:

1) між реалізаціями об'єктів;

2) між покажчиками реалізації об'єктів;

3) між формальними та фактичними параметрами.

Однак дуже важливо пам'ятати, що у всіх трьох формах сумісність типів розширюється лише від нащадка до батька. Інакше кажучи, дочірні типи можуть вільно використовуватися замість батьківських, але з навпаки.

Наприклад, TSalaried є нащадком TEmployee, а ТСош-missioned - нащадком TSlaried. Пам'ятаючи про це, розглянемо такі описи:

tyрe

PEmрloyee = ^TEmployee;

PSalaried = ^TSalaried;

PCommissioned = ^TCommissioned;

було

AnEmрloyee: TEmployee;

ASalaried: TSalaried;

PCommissioned: TCommissioned;

TEmployeePtr: PEmрloyee;

TSalariedPtr: PSalaried;

TCommissionedPtr: PCommissioned;

За даними описами справедливі наступні оператори

присвоєння:

AnEmрloyee :=ASalaried;

ASalaried := ACommissioned;

TCommissionedPtr := ACommissioned;

Примітка

Об'єкту, що породжує, можна присвоїти екземпляр будь-якого з його породжених типів. Зворотні присвоєння неприпустимі.

Ця концепція є новою для Pascal, і спочатку, можливо, важко запам'ятати, в якому порядку слід сумісність типів. Необхідно думати так: джерело має бути в змозі повністю заповнити приймач. Породжені типи містять все, що містять їх типи, що породжують завдяки властивості успадкування. Тому породжений тип має або точно такий самий розмір, або (що найчастіше і буває) він більше свого батька, але ніколи не буває менше. Присвоєння об'єкта, що породжує (батьківського), породженому (дочірньому) могло б залишити деякі поля породженого об'єкта невизначеними, що небезпечно і тому неприпустимо.

В операторах привласнення з джерела до приймача копіюватимуться лише поля, які є спільними для обох типів. В операторі присвоєння:

AnEmployee:= ACommissioned;

Тільки поля Name, Title і Rate з ACommissioned будуть скопійовані в AnEmployee, оскільки ці поля є спільними для TCommissioned і TEmployee. Сумісність типів працює також між покажчиками на типи об'єктів і підпорядковується тим самим загальним правилам, як і реалізації об'єктів. Покажчик на нащадка може бути присвоєно покажчику на батька. Якщо дати попередні визначення, то наступні надання покажчиків будуть допустимими:

TSalariedPtr:= TCommissionedPtr;

TEmployeePtr:= TSalariedPtr;

TEmployeePtr:= PCommissionedPtr;

Слід пам'ятати, що зворотні присвоєння неприпустимі!

Формальний параметр (або значення, або параметр-змінна) даного об'єктного типу може приймати як фактичний параметр об'єкт свого ж типу або об'єкти всіх дочірніх типів. Якщо визначити заголовок процедури наступним чином:

procedure CalcFedTax(Victim: TSalaried);

то допустимими типами фактичних параметрів можуть бути TSalaried або TCommissioned, але не тип TEmployee. Victim також може бути параметром-змінною. При цьому виконуються самі правила сумісності.

зауваження

Між параметрами-значеннями та параметрами-змінними є докорінна відмінність. Параметр-значення є вказівником на дійсний об'єкт, що посідається як параметр, тоді як параметр-змінна є тільки копією фактичного параметра. Більше того, ця копія включає лише ті поля, які входять до типу формального параметра-значення. Це означає, що фактичний параметр буквально перетворюється на тип формального параметра. Параметр-змінна більше нагадує приведення до зразка, тому, що фактичний параметр залишається незмінним.

Аналогічно, якщо формальний параметр є вказівником типу об'єкта, фактичний параметр може бути покажчиком цей тип об'єкта чи будь-який дочірній тип. Нехай дано заголовок процедури:

procedure Worker.Add (AWorker: PSalaried);

Тоді допустимими типами фактичних параметрів можуть бути PSalaried або PCommissioned, але не тип PEmployee.

ЛЕКЦІЯ № 14. Асемблер

1. Про асемблера

Колись асемблер був мовою, без знання якої не можна було змусити комп'ютер зробити щось корисне. Поступово ситуація змінювалася. З'являлися зручніші засоби спілкування з комп'ютером. Але на відміну від інших мов асемблер не вмирав, більше того, він не міг зробити цього в принципі. Чому? У пошуках відповіді спробуємо зрозуміти, що таке мова асемблера взагалі.

Якщо коротко, то мова асемблера – це символічне уявлення машинної мови. Всі процеси в машині на найнижчому апаратному рівні приводяться в дію тільки командами (інструкціями) машинної мови. Звідси зрозуміло, що, незважаючи на загальну назву, мова асемблера для кожного типу комп'ютера своя. Це стосується і зовнішнього вигляду програм, написаних на асемблері, та ідей, відображенням яких ця мова є.

По-справжньому вирішити проблеми, пов'язані з апаратурою (або навіть більше, що залежать від апаратури, як, наприклад, підвищення швидкодії програми), неможливо без знання асемблера.

Програміст або будь-який інший користувач можуть використовувати будь-які високорівневі засоби аж до програм побудови віртуальних світів і, можливо, навіть не підозрювати, що насправді комп'ютер виконує не команди мови, якою написана його програма, а їх трансформоване уявлення у формі нудної та похмурої послідовності команд зовсім іншої мови – машинної. А тепер уявімо, що у такого користувача виникла нестандартна проблема. Наприклад, його програма повинна працювати з деяким незвичайним пристроєм або виконувати інші дії, що вимагають знання принципів роботи комп'ютера. Якою б гарною не була мова, якою програміст написав свою програму, без знання асемблера йому не обійтися. І невипадково практично всі компілятори мов високого рівня містять засоби зв'язку своїх модулів з модулями на асемблері або підтримують вихід асемблерний рівень програмування.

Комп'ютер складається з кількох фізичних пристроїв, кожен з яких підключено до одного блоку, що називається системним. Щоб зрозуміти їхнє функціональне призначення, подивимося на структурну схему типового комп'ютера (рис. 1). Вона не претендує на безумовну точність і має на меті лише показати призначення, взаємозв'язок та типовий склад елементів сучасного персонального комп'ютера.

Рис. 1. Структурна схема персонального комп'ютера

2. Програмна модель мікропроцесора

На сучасному комп'ютерному ринку спостерігається велика різноманітність різних типів комп'ютерів. Тому можна припустити виникнення у споживача питання у тому, як оцінити можливості конкретного типу (чи моделі) комп'ютера та її відмінні риси від комп'ютерів інших типів (моделей). Щоб зібрати докупи всі поняття, що характеризують комп'ютер з погляду його функціональних програмно-керованих властивостей, існує спеціальний термін - архітектура ЕОМ. Вперше поняття архітектура ЕОМ стало згадуватися з появою машин 3-го покоління для їхньої порівняльної оцінки.

До вивчення мови асемблера будь-якого комп'ютера має сенс приступати тільки після з'ясування того, яка частина комп'ютера залишена видимою та доступною для програмування цією мовою. Це так звана програмна модель комп'ютера, частиною якої є програмна модель мікропроцесора, яка містить тридцять два регістри, в тій чи іншій мірі доступні для використання програмістом.

Дані регістри можна розділити на великі групи:

1) 6 користувальницьких регістрів;

2) 16 системних регістрів.

3. Регістри користувача

Як випливає з назви, регістри користувача називаються тому, що програміст може використовувати їх при написанні своїх програм. До цих регістрів відносяться (рис. 2):

1) вісім 32-бітових регістрів, які можуть використовуватися програмістами для зберігання даних та адрес (їх ще називають регістрами загального призначення (РОН)):

eax/ax/ah/al;

ebx/bx/bh/bl;

edx/dx/dh/dl;

ecx/cx/ch/cl;

ebp/bp;

esi/si;

edi/di;

esp/sp.

2) шість регістрів сегментів: cs, ds, ss, es, fs, gs;

3) регістри стану та управління:

регістр прапорів eflags/flags;

регістр покажчика команди eip/ip.

Рис. 2. Регістри користувача

Багато з цих регістрів наведені з похилою роздільною рисою. Це не різні регістри – це частини одного великого 32-розрядного регістру. Їх можна використовувати у програмі як окремі об'єкти.

4. Реєстри загального призначення

Усі регістри цієї групи дозволяють звертатися до своїх "молодших" частин. Використовувати для самостійної адресації можна лише молодші 16- та 8-бітові частини цих регістрів. Старші 16 біт цих регістрів як самостійні об'єкти недоступні.

Перерахуємо регістри, які стосуються групи регістрів загального призначення. Оскільки ці регістри фізично перебувають у мікропроцесорі всередині арифметико-логического устрою (АЛ>), їх ще називають регістрами АЛУ:

1) eax/ax/ah/al (Accumulator register) – акумулятор. Застосовується для зберігання проміжних даних. У деяких командах використання цього регістру є обов'язковим;

2) ebx/bx/bh/bl (Base register) – базовий регістр. Застосовується для зберігання базової адреси деякого об'єкта пам'яті;

3) ecx/cx/ch/cl (Count register) – регістр-лічильник. Застосовується в командах, які роблять деякі дії, що повторюються. Його використання найчастіше неявно та приховано в алгоритмі роботи відповідної команди.

Наприклад, команда організації циклу loop, крім передачі управління команді, що знаходиться за деякою адресою, аналізує та зменшує на одиницю значення регістру есх/сх;

4) edx/dx/dh/dl (Data register) – регістр даних.

Так само, як і регістр eax/ax/ah/al, він зберігає проміжні дані. У деяких командах використання обов'язково; для деяких команд це відбувається неявно.

Наступні два регістри використовуються для підтримки так званих ланцюжкових операцій, тобто операцій, які проводять послідовну обробку ланцюжків елементів, кожен з яких може мати довжину 32, 16 або 8 біт:

1) esi/si (Source Index register) – індекс джерела.

Цей регістр у ланцюжкових операціях містить поточну адресу елемента в ланцюжку-джерелі;

2) edi/di (Destination Index register) – індекс приймача (одержувача). Цей регістр у ланцюжкових операціях містить поточну адресу в ланцюжку-приймачі.

У архітектурі мікропроцесора на програмно-апаратному рівні підтримується така структура даних, як стек. Для роботи зі стеком у системі команд мікропроцесора є спеціальні команди, а програмної моделі мікропроцесора для цього існують спеціальні регістри:

1) esp/sp (Stack Pointer register) – регістр покажчика стека. Містить вказівник вершини стека у поточному сегменті стека.

2) ebp/bp (Base Pointer register) - регістр покажчика бази кадру стека. Призначений для організації довільного доступу до даних усередині стека.

Використання жорсткого закріплення регістрів для деяких команд дозволяє компактніше кодувати їх машинне подання. Знання цих особливостей дозволить за необхідності хоча б кілька байт заощадити пам'ять, займану кодом програми.

5. Сегментні регістри

У програмній моделі мікропроцесора є шість сегментних регістрів: cs, ss, ds, es, gs, fs.

Їх існування обумовлено специфікою організації та використання оперативної пам'яті мікропроцесорами Intel. Вона у тому, що мікропроцесор апаратно підтримує структурну організацію програми як трьох частин, званих сегментами. Відповідно така організація пам'яті називається сегментною.

Для того щоб вказати на сегменти, до яких програма має доступ у конкретний момент часу, та призначені сегментні регістри. Фактично (з невеликою поправкою) у цих регістрах містяться адреси пам'яті, з яких починаються відповідні сегменти. Логіка обробки машинної команди побудована так, що при вибірці команди, доступі до даних програми або стеку неявно використовуються адреси в цілком певних сегментних регістрах.

Мікропроцесор підтримує такі типи сегментів.

1. Сегмент коду. Містить команди програми. Для доступу до цього сегменту служить регістр cs (code segment register) – сегментний регістр коду. Він містить адресу сегмента з машинними командами, якого має доступ мікропроцесор (тобто. ці команди завантажуються в конвеєр мікропроцесора).

2. Сегмент даних. Містить дані, що обробляються програмою. Для доступу до цього сегменту служить регістр ds (data segment register) – сегментний регістр даних, який зберігає адресу сегмента даних поточної програми.

3. Сегмент стека. Цей сегмент є область пам'яті, звану стеком. Роботу зі стеком мікропроцесор організує за таким принципом: останній записаний у цю область елемент вибирається першим. Для доступу до цього сегменту служить регістр ss (stack segment register) – сегментний регістр стека, що містить адресу сегмента стека.

4. Додатковий сегмент даних. Не явно алгоритми виконання більшості машинних команд припускають, що дані, які вони обробляють, розташовані в сегменті даних, адреса якого знаходиться в сегментному регістрі ds. Якщо програмі недостатньо одного сегмента даних, вона має можливість використовувати ще три додаткових сегмента даних. Але на відміну від основного сегмента даних, адреса якого міститься в сегментному регістрі ds, при використанні додаткових сегментів даних, їх адреси потрібно вказувати явно за допомогою спеціальних префіксів перевизначення сегментів у команді. Адреси додаткових сегментів даних повинні міститися в регістрах es, gs, fs (extension data segment registers).

6. Регістри стану та управління

У мікропроцесор включено кілька регістрів, які постійно містять інформацію про стан як самого мікропроцесора, так і програми, команди якої зараз завантажені на конвеєр. До цих регістрів відносяться:

1) регістр прапорів eflags/flags;

2) регістр покажчика команди eip/ip.

Використовуючи ці регістри, можна отримувати інформацію про результати виконання команд і проводити стан самого мікропроцесора. Розглянемо докладніше призначення та вміст цих регістрів

1. eflags/flags (flag register) – регістр прапорів. Розрядність eflags/flags – 32/16 біт. Окремі біти даного регістру мають певне функціональне призначення та називаються прапорами. Молодша частина цього регістру повністю аналогічна регістру flags для 18086. На малюнку 3 показано вміст регістру eflags.

Рис. 3. Вміст регістру eflags

Виходячи з особливостей використання прапора регістру eflags/flags можна поділити на три групи:

1) вісім прапорів стану.

Ці прапори можуть змінюватись після виконання машинних команд. Прапори стану регістру eflags відбивають особливості результату виконання арифметичних чи логічних операцій. Це дає можливість аналізувати стан обчислювального процесу та реагувати на нього за допомогою команд умовних переходів та викликів підпрограм. У таблиці 1 наведено прапори стану та зазначено їх призначення.

2) один прапор управління.

Позначається df (Directory Flag). Він знаходиться в 10-му біті регістру eflags і використовується ланцюжковими командами. Значення прапора df визначає напрямок поелементної обробки в цих операціях: від початку рядка до кінця (df = 0) або навпаки, від кінця рядка до її початку (df = 1). Для роботи із прапором df існують спеціальні команди: eld (зняти прапор df) та std (установити прапор df). Застосування цих команд дозволяє привести прапор df у відповідність до алгоритму та забезпечити автоматичне збільшення або зменшення лічильників при виконанні операцій з рядками.

3) п'ять системних прапорів.

Керують введенням-виводом, перериваннями, що маскуються, налагодженням, перемиканням між завданнями і віртуальним режимом 8086. Прикладним програмам не рекомендується модифікувати без необхідності ці прапори, так як в більшості випадків це призведе до переривання роботи програми. У таблиці 2 наведено системні прапори, їх призначення.

Таблиця 1. Прапори стануТаблиця 2. Системні прапори

2. eip/ip (Instraction Pointer register) - регістр-покажчик команд. Регістр eip/ip має розрядність 32/16 біт і містить зміщення наступної команди, що підлягає виконанню щодо вмісту сегментного регістру cs в поточному сегменті команд. Цей регістр безпосередньо недоступний програмісту, але завантаження та зміна його значення виконуються різними командами управління, до яких належать команди умовних та безумовних переходів, виклику процедур та повернення з процедур. Виникнення переривань також призводить до модифікації регістру eip/ip.

ЛЕКЦІЯ №15. Реєстри

1. Системні регістри мікропроцесора

Сама назва цих регістрів свідчить, що вони виконують специфічні функції у системі. Використання системних регістрів жорстко регламентовано. Саме вони забезпечують роботу захищеного режиму. Їх також можна розглядати як частину архітектури мікропроцесора, яка навмисно залишена видимою для того, щоб кваліфікований системний програміст міг виконати найнижчі рівні.

Системні регістри можна поділити на три групи:

1) чотири регістри управління;

2) чотири регістри системних адрес;

3) вісім регістрів налагодження.

2. Реєстри управління

До групи регістрів управління входять чотири регістри: cr0, cr1, cr2, cr3. Ці регістри призначені для управління системою. Регістри керування доступні лише програмам із рівнем привілеїв 0.

Хоча мікропроцесор має чотири регістри управління, доступними є лише три з них - виключається cr1, функції якого поки що не визначені (він зарезервований для майбутнього використання).

Регістр cr0 містить системні прапори, що керують режимами роботи мікропроцесора і відображають його стан глобально, незалежно від конкретних завдань.

Призначення системних прапорів:

1) ре (Protect Enable), біт 0 – дозвіл захищеного режиму роботи. Стан цього прапора показує, у якому з двох режимів - реальному (ре = 0) чи захищеному (ре = 1) - працює мікропроцесор у час;

2) mp (Math Present), біт 1 – наявність співпроцесора. Завжди 1;

3) ts (Task Switched), біт 3 - перемикання завдань. Процесор автоматично встановлює цей біт при перемиканні виконання іншого завдання;

4) am (Alignment Mask), біт 18 - маска вирівнювання. Цей біт дозволяє (am = 1) чи забороняє (am = 0) контроль вирівнювання;

5) cd (Cache Disable), біт 30 – заборона кеш-пам'яті.

За допомогою цього біта можна заборонити (cd = 1) або дозволити (cd = 0) використання внутрішньої кеш-пам'яті (кеш-пам'яті першого рівня);

6) pg (PaGing), біт 31 - дозвіл (pg = 1) або заборона (pg = 0) сторінкового перетворення.

Прапор використовується при сторінки моделі пам'яті.

Регістр cr2 використовується при сторінці оперативної пам'яті для реєстрації ситуації, коли поточна команда звернулася за адресою, що міститься в сторінці пам'яті, відсутньої в даний момент часу в пам'яті.

У такій ситуації в мікропроцесорі виникає виняткова ситуація з номером 14, і лінійна 32-бітна адреса команди, що викликала цей виняток, записується в регістр cr2. Маючи цю інформацію, обробник виключення 14 визначає потрібну сторінку, здійснює її підкачування на згадку і відновлює нормальну роботу програми;

Реєстр cr3 також використовується при сторінці пам'яті. Це так званий регістр каталогу сторінок першого рівня. Він містить 20-бітну фізичну базову адресу каталогу сторінок поточного завдання. Цей каталог містить 1024 32-бітових дескрипторів, кожен з яких містить адресу таблиці сторінок другого рівня. У свою чергу, кожна з таблиць сторінок другого рівня містить 1024 32-бітових дескрипторів, що адресують сторінкові кадри в пам'яті. Розмір сторінкового кадру – 4 Кбайти.

3. Реєстри системних адрес

Ці регістри ще називають регістрами управління пам'яттю.

Вони призначені для захисту програм та даних у мультизадачному режимі роботи мікропроцесора. При роботі в захищеному режимі мікропроцесора адресний простір поділяється на:

1) глобальне – загальне для всіх завдань;

2) локальне – окреме для кожного завдання.

Цим поділом і пояснюється присутність в архітектурі мікропроцесора наступних системних регістрів:

1) регістра таблиці глобальних дескрипторів gdtr (Global Descriptor Table Register), що має розмір 48 біт і містить 32-бітову (біти 16-47) базову адресу глобальної дескрипторної таблиці GDT і 16-бітове (біти 0-15) значення межі, що представляє собою розмір у байтах таблиці GDT;

2) регістра таблиці локальних дескрипторів ldtr (Local Descriptor Table Register), що має розмір 16 біт і містить так званий селектор дескриптора локальної дескрипторної таблиці LDT Цей селектор є покажчиком у таблиці GDT, який описує сегмент, що містить локальну дескрипторну таблицю;

3) регістра таблиці дескрипторів переривань idtr (Interrupt Descriptor Table Register), що має розмір 48 біт і містить 32-бітовий (біти 16-47) базову адресу дескрипторної таблиці переривань IDT і 16-бітове (біти 0-15) значення межі, розмір у байтах таблиці IDT;

4) 16-бітового регістра задачі tr (Task Register), який подібно до регістру ldtr, містить селектор, тобто покажчик на дескриптор у таблиці GDT Цей дескриптор описує поточний сегмент стану завдання (TSS - Task Segment Status). Цей сегмент створюється для кожного завдання у системі, має жорстко регламентовану структуру та містить контекст (поточний стан) завдання. Основне призначення сегментів TSS - зберігати поточний стан завдання у момент перемикання інше завдання.

4. ​​Реєстри налагодження

Це дуже цікава група регістрів, призначених для апаратного налагодження. Засоби апаратного налагодження вперше з'явилися в мікропроцесорі i486. Апаратно мікропроцесор містить вісім регістрів налагодження, але реально з них використовуються лише шість.

Регістри dr0, dr1, dr2, dr3 мають розрядність 32 біта і призначені для завдання лінійних адрес чотирьох точок переривання. Використовуваний при цьому механізм наступний: будь-який формується адресою, що формується поточною програмою, порівнюється з адресами в регістрах dr0 ... dr3, і при збігу генерується виключення налагодження з номером 1.

Регістр dr6 називається регістром стану налагодження. Біти цього регістру встановлюються відповідно до причин, що викликали виникнення останнього виключення з номером 1.

Перерахуємо ці біти та їх призначення:

1) b0 - якщо цей біт встановлено в 1, то останній виняток (переривання) виникло в результаті досягнення контрольної точки, визначеної в регістрі dr0;

2) b1 - аналогічно b0, але для контрольної точки в регістрі dr1;

3) b2 - аналогічно b0, але для контрольної точки в регістрі dr2;

4) bЗ - аналогічно b0, але для контрольної точки в регістрі dr3;

5) bd (біт 13) – служить для захисту регістрів налагодження;

6) bs (біт 14) - встановлюється в 1, якщо виняток 1 був викликаний станом прапора tf = 1 в регістрі eflags;

7) bt (біт 15) встановлюється в 1, якщо виняток 1 було викликано перемиканням на завдання з встановленим бітом пастки TSS t = 1.

Всі інші біти у цьому регістрі заповнюються нулями. Обробник виключення 1 за вмістом dr6 повинен визначити причину, за якою стався виняток, та виконати необхідні дії.

Регістр dr7 називається регістром управління налагодженням. У ньому для кожного з чотирьох регістрів контрольних точок налагодження є поля, що дозволяють уточнити такі умови, за яких слід згенерувати переривання:

1) місце реєстрації контрольної точки - тільки в поточному завданні або будь-якому завданні. Ці біти займають молодші 8 біт регістра dr7 (по 2 біти на кожну контрольну точку (фактично точку переривання), що задається регістрами dr0, dr1, dr2, dr3 відповідно).

Перший біт з кожної пари - це так званий локальний дозвіл; його установка свідчить, що точка переривання діє, якщо вона перебуває у межах адресного простору поточного завдання.

Другий біт у кожній парі визначає глобальне дозвіл, яке свідчить, що дана контрольна точка діє межах адресних просторів всіх завдань, що у системі;

2) тип доступу, за яким ініціюється переривання: тільки під час вибірки команди, під час запису чи під час запису / читання даних. Біти, що визначають подібну природу виникнення переривання, локалізуються у старшій частині цього регістру. Більшість із системних регістрів програмно доступна.

лекція № 16. Програми на асемблері

1. Структура програми на асемблері

Програма на асемблері є сукупність блоків пам'яті, званих сегментами пам'яті. Програма може складатися з одного або кількох блоків-сегментів. Кожен сегмент містить сукупність речень мови, кожна з яких займає окремий рядок коду програми.

Пропозиції асемблера бувають чотирьох типів:

1) команди або інструкції, що є символічними аналогами машинних команд. У процесі трансляції інструкції асемблера перетворюються на відповідні команди системи команд мікропроцесора;

2) макрокоманди. Це оформлюються певним чином речення тексту програми, що заміщуються під час трансляції іншими реченнями;

3) директиви, які є вказівкою транслятора асемблера виконання деяких дій. У директив немає аналогів у машинному поданні;

4) рядки коментарів, що містять будь-які символи, у тому числі літери російського алфавіту. Коментарі ігноруються транслятором.

2. Синтаксис асемблера

Пропозиції, що становлять програму, можуть бути синтаксичною конструкцією, що відповідає команді, макрокоманді, директиві або коментарю. Для того, щоб транслятор асемблера міг розпізнати їх, вони повинні формуватися за певними синтаксичними правилами. Для цього найкраще використовувати формальний опис синтаксису мови на кшталт правил граматики. Найбільш поширені способи подібного опису мови програмування – синтаксичні діаграми та розширені форми Бекуса-Наура. Для практичного використання зручніші синтаксичні діаграми. Наприклад, синтаксис речень асемблера можна описати за допомогою синтаксичних діаграм, показаних на наступних малюнках.

Рис. 4. Формат пропозиції асемблера

Рис. 5. Формат директив

Рис. 6. Формат команд та макрокоманд

На цих малюнках:

1) ім'я мітки - ідентифікатор, значенням якого є адреса першого байта пропозиції вихідного тексту програми, яке він позначає;

2) ім'я - ідентифікатор, який відрізняє цю директиву від інших однойменних директив. Через війну обробки асемблером певної директиви цього імені може бути присвоєно певні характеристики;

3) код операції (КОП) та директива – це мнемонічні позначення відповідної машинної команди, макрокоманди або директиви транслятора;

4) операнди - частини команди, макрокоманди чи директиви асемблера, що позначають об'єкти, з яких виконуються дії. Операнди асемблера описуються виразами з числовими та текстовими константами, мітками та ідентифікаторами змінних з використанням знаків операцій та деяких зарезервованих слів.

Як використовувати синтаксичні діаграми? Дуже просто: для цього потрібно лише знайти і потім пройти шлях від входу діаграми (ліворуч) до її виходу (праворуч). Якщо такий шлях існує, то пропозиція чи конструкція синтаксично правильні. Якщо такого шляху немає, то цю конструкцію компілятор не прийме. При роботі з синтаксичними діаграмами звертайте увагу на напрямок обходу, що вказується стрілками, оскільки серед шляхів можуть бути такі, якими можна йти праворуч наліво. Власне, синтаксичні діаграми відбивають логіку роботи транслятора під час аналізу вхідних пропозицій програми.

Допустимими символами при написанні тексту програм є:

1) всі латинські літери: А – Z, а – z. При цьому великі та малі літери вважаються еквівалентними;

2) цифри від 0 до 9;

3) знаки?, @, S, _, &;

4) роздільники.

Пропозиції асемблера формуються з лексем, що є синтаксично нероздільні послідовності допустимих символів мови, що мають сенс для транслятора.

Лексемами є такі.

1. Ідентифікатори - послідовності допустимих символів, що використовуються для позначення таких об'єктів програми, як коди операцій, імена змінних та назви міток. Правило запису ідентифікаторів полягає в наступному: ідентифікатор може складатися з одного або кількох символів. Як символи можна використовувати літери латинського алфавіту, цифри та деякі спеціальні знаки - _, ?, $, @. Ідентифікатор не може починатися символом цифри. Довжина ідентифікатора може бути до 255 символів, хоча транслятор сприймає лише перші 32, а решту ігнорує. Регулювати довжину можливих ідентифікаторів можна за допомогою опції командного рядка mv. Крім цього, існує можливість вказати транслятору на те, щоб він розрізняв великі і малі літери або ігнорував їхню відмінність (що і робиться за умовчанням). Для цього використовуються опції командного рядка /mu, /ml, /mx.

2. Ланцюжки символів – послідовності символів, укладені в одинарні чи подвійні лапки.

3. Цілі числа в одній із наступних систем числення: двійкову, десяткову, шістнадцяткову. Ототожнення чисел під час запису в програмах на асемблері проводиться у разі певним правилам:

1) десяткові числа не вимагають для свого ототожнення вказівки будь-яких додаткових символів, наприклад, 25 або 139;

2) для ототожнення у вихідному тексті програми двійкових чисел необхідно після запису нулів та одиниць, що входять до їх складу, поставити латинське "b", наприклад, 10010101 b;

3) Шістнадцяткові числа мають більше умовностей при своєму записі:

а) по-перше, вони складаються з цифр 0 ... 9, малих і великих букв латинського алфавіту а, b, с, d, е, Гілі Д В, С, D, Е, Е

б) по-друге, у транслятора можуть виникнути труднощі з розпізнаванням шістнадцяткових чисел через те, що вони можуть складатися як з одних цифр 0...9 (наприклад, 190845), так і починатися з літери латинського алфавіту (наприклад, efl5 ). Для того, щоб "пояснити" транслятору, що дана лексема не є десятковим чи ідентифікатором, програміст повинен спеціальним чином виділяти шістнадцяткове число. Для цього на кінці послідовності шістнадцяткових цифр, що становлять шістнадцяткове число, записують латинську букву "h". Це обов'язкова умова. Якщо шістнадцяткове число починається з літери, перед ним записується провідний нуль: 0 efl5 h.

Таким чином ми розібралися з тим, як конструюються пропозиції програми асемблера. Але це лише поверховий погляд.

Практично кожна пропозиція містить опис об'єкта, над яким або за допомогою якого виконується певна дія. Ці об'єкти називаються операндами. Їх можна визначити так: операнди - це об'єкти (деякі значення, регістри чи осередки пам'яті), куди діють інструкції чи директиви, чи це об'єкти, які визначають чи уточнюють дію інструкцій чи директив.

Операнди можуть комбінуватися з арифметичними, логічними, побітовими та атрибутивними операторами для розрахунку деякого значення або визначення осередку пам'яті, на яку впливатиме дана команда або директива.

Розглянемо докладніше характеристику операндів у наведеній нижче класифікації:

1) постійні чи безпосередні операнди - число, рядок, ім'я чи вираз, мають деяке фіксоване значення. Ім'я не повинно переміщуватися, тобто залежати від адреси завантаження програми в пам'ять. Наприклад, може бути визначено операторами equ чи =;

2) адресні операнди, що задають фізичне розташування операнда в пам'яті за допомогою вказівки двох складових адреси: сегмента та зміщення (рис. 7);

Рис. 7. Синтаксис опису адресних операндів

3) операнди, що переміщуються - будь-які символьні імена, що представляють деякі адреси пам'яті. Ці адреси можуть позначати розташування в пам'яті деяких інструкцій (якщо операнд – мітка) або даних (якщо операнд – ім'я області пам'яті в сегменті даних).

Операнди, що переміщуються, відрізняються від адресних тим, що вони не прив'язані до конкретної адреси фізичної пам'яті. Сегментна складова адреси операнда, що переміщується, невідома і буде визначена після завантаження програми в пам'ять для виконання.

Лічильник адреси – специфічний вид операнда. Він позначається знаком S. Специфіка цього операнда в тому, що коли транслятор асемблера зустрічає у вихідній програмі цей символ, він підставляє замість нього поточне значення лічильника адреси. Значення лічильника адреси або, як його іноді називають, лічильника розміщення являє собою усунення поточної машинної команди щодо початку сегмента коду. У форматі лістингу лічильнику адреси відповідає друга чи третя колонка (залежно від цього, є чи ні у лістингу колонка з рівнем вкладеності). Якщо взяти за приклад будь-який лістинг, то видно, що при обробці транслятором чергової команди асемблера лічильник адреси збільшується на довжину сформованої машинної команди. Важливо правильно розуміти цей момент. Наприклад, обробка директив асемблера не тягне у себе зміни лічильника. Директиви, на відміну команд ассемблера, - це лише вказівки транслятору виконання певних дій з формуванню машинного представлення програми, й них транслятором не генерується жодних конструкцій у пам'яті.

При використанні подібного виразу для переходу не забувайте про довжину самої команди, в якій цей вираз використовується, оскільки значення лічильника адреси відповідає зсуву в сегменті команд цієї, а не наступної команди. У нашому прикладі команда jmp займає 2 байти. Але будьте обережні, довжина команди залежить від того, які використовуються операнди. Команда з реєстровими операндами буде коротшою за команду, один з операндів якої розташований на згадку. У більшості випадків цю інформацію можна отримати, знаючи формат машинної команди та аналізуючи колонку лістингу з об'єктним кодом команди;

4) регістровий операнд - це ім'я регістра. У програмі на асемблері можна використовувати імена всіх регістрів загального призначення та більшості системних регістрів;

5) базовий та індексний операнди. Цей тип операндів використовується для реалізації непрямої базової, непрямої індексної адресації або їх комбінацій та розширень;

6) структурні операнди використовуються доступу до конкретному елементу складного типу даних, званого структурою.

Записи (подібно до структурного типу) використовуються для доступу до бітового поля деякого запису.

Операнди є елементарними компонентами, у тому числі формується частина машинної команди, що означає об'єкти, з яких виконується операція. У загальному випадку операнди можуть входити як складові у складніші освіти, звані висловлюваннями. Вирази є комбінації операндів і операторів, що розглядаються як єдине ціле. Результатом обчислення виразу може бути адреса деякої комірки пам'яті або деяке константне (абсолютне) значення.

Можливі типи операнда ми вже розглянули. Перерахуємо тепер можливі типи операторів асемблера та синтаксичні правила формування виразів асемблера, і дамо коротку характеристику операторів.

1. Арифметичні оператори. До них відносяться:

1) унарні "+" та "-";

2) бінарні "+" та "-";

3) множення "*";

4) цілісного розподілу "/";

5) отримання залишку від розподілу "mod".

Ці оператори розташовані на рівнях пріоритету 6,7,8 таблиці 4.

Рис. 8. Синтаксис арифметичних операцій

2. Оператори зсуву виконують зсув виразу на вказану кількість розрядів (рис. 9).

Рис. 9. Синтаксис операторів зсуву

3. Оператори порівняння (повертають значення "істина" або "брехня") призначені для формування логічних виразів (рис. 10 та табл. 3). Логічне значення "істина" відповідає цифровій одиниці, а "брехня" - нулю.

Рис. 10. Синтаксис операторів порівняння

Таблиця 3. Оператори порівняння

4. Логічні оператори виконують над виразами побітові операції (рис. 11). Вирази мають бути абсолютними, тобто такими, чисельне значення яких може бути обчислено транслятором.

Рис. 11. Синтаксис логічних операторів

5. Індексний оператор []. Дужки теж є оператором, і транслятор їх наявність сприймає як вказівку скласти значення 1 за цими дужками з 2, укладеним у дужки (рис. 12).

Рис. 12. Синтаксис індексного оператора

Зауважимо, що у літературі з асемблеру прийнято таке позначення: як у тексті йдеться про вміст регістру, його назву беруть у круглі дужки. Ми також дотримуватимемося цього позначення.

6. Оператор перевизначення типу ptr застосовується для перевизначення або уточнення типу мітки або змінної, що визначаються виразом (рис. 13).

Тип може набувати одне з наступних значень: byte, word, dword, qword, tbyte, near, far.

Рис. 13. Синтаксис оператора перевизначення типу

7. Оператор перевизначення сегмента ":" (двокрапка) змушує обчислювати фізичну адресу щодо конкретно задається сегментної складової: "ім'я сегментного регістру", "ім'я сегмента" з відповідної директиви SEGMENT або "ім'я групи" (рис. 14). Під час обговорення сегментації ми говорили, що мікропроцесор на апаратному рівні підтримує три типи сегментів - коду, стека і даних. У чому така апаратна підтримка? Наприклад, для вибірки виконання чергової команди мікропроцесор повинен обов'язково подивитися вміст сегментного регістру cs і його. А в цьому регістрі, як ми знаємо, міститься (поки що ще не зрушена) фізична адреса початку сегмента команд. Для отримання адреси конкретної команди мікропроцесору залишається помножити вміст cs на 16 (що означає зсув на чотири розряди) і скласти отримане значення 20-біт з 16-бітним вмістом регістра ip. Приблизно те саме відбувається і тоді, коли мікропроцесор обробляє операнди в машинній команді. Якщо він бачить, що операнд – це адреса (ефективна адреса, яка є лише частиною фізичної адреси), то він знає, в якому сегменті його шукати, – за умовчанням це сегмент, адреса початку якого записана у сегментному регістрі ds.

А що ж із сегментом стека? У контексті нашого розгляду нас цікавлять регістри sp та Ър. Якщо мікропроцесор бачить як операнда (або його частини, якщо операнд - вираз) один з цих регістрів, то за умовчанням він формує фізичну адресу операнда, використовуючи як його сегментну складову вміст регістру ss. Це набір мікропрограм у блоці мікропрограмного керування, кожна з яких виконує одну з команд у системі машинних команд мікропроцесора. Кожна мікропрограма працює за своїм алгоритмом. Змінити його, звичайно, не можна, але можна трохи підкоригувати. Це робиться за допомогою необов'язкового поля префікса машинної команди. Якщо ми погоджуємося з тим, як працює команда, то це поле відсутнє. Якщо ж ми хочемо внести поправку (якщо, звичайно, вона допустима для конкретної команди) в алгоритм роботи команди, необхідно сформувати відповідний префікс.

Префікс є однобайтовою величиною, чисельне значення якої визначає її призначення. Мікропроцесор розпізнає за вказаним значенням, що цей байт є префіксом, і подальша робота мікропрограми виконується з урахуванням надійшло вказівки на коригування її роботи. Нині нас цікавить один із них – префікс заміни (перевизначення) сегменту. Його призначення полягає в тому, щоб вказати мікропроцесору (а по суті мікропрограмі) на те, що ми не хочемо використовувати сегмент за замовчуванням. Можливості для такого перевизначення, звісно, ​​обмежені. Сегмент команд перевизначити не можна, адреса чергової команди однозначно визначається парою cs: ip. А ось сегменти стеку та даних – можна. Для цього і призначений оператор ":". Транслятор асемблера, обробляючи цей оператор, формує відповідний однобайтовий префікс заміни сегмента.

Рис. 14. Синтаксис оператора перевизначення сегмента

8. Оператор іменування типу структури "."(точка) також змушує транслятор проводити певні обчислення, якщо він зустрічається у виразі.

9. Оператор отримання сегментної складової адреси виразу seg повертає фізичну адресу сегмента для виразу (рис. 15), якою можуть виступати мітка, змінна, ім'я сегмента, ім'я групи або деяке символічне ім'я.

Рис. 15. Синтаксис оператора отримання сегментної складової

10. Оператор отримання зміщення виразу offset дозволяє отримати значення зміщення виразу (рис. 16) в байтах щодо початку сегмента, в якому вираз визначено.

Рис. 16. Синтаксис оператора отримання усунення

Як і в мовах високого рівня, виконання операторів асемблера при обчисленні виразів здійснюється відповідно до їх пріоритетів (табл. 4). Операції з однаковими пріоритетами виконуються послідовно зліва направо. Зміна порядку виконання можлива шляхом розміщення круглих дужок, які мають найвищий пріоритет.

Таблиця 4. Оператори та їх пріоритет

3. Директиви сегментації

Під час попереднього обговорення ми з'ясували всі основні правила запису команд та операндів у програмі на асемблері. Відкритим залишилося питання, як правильно оформити послідовність команд, щоб транслятор міг їх обробити, а мікропроцесор - виконати.

При розгляді архітектури мікропроцесора ми дізналися, що він має шість сегментних регістрів, за допомогою яких може працювати одночасно:

1) з одним сегментом коду;

2) з одним сегментом стека;

3) з одним сегментом даних;

4) із трьома додатковими сегментами даних.

Ще раз пригадаємо, що фізично сегмент є область пам'яті, зайняту командами та (або) даними, адреси яких обчислюються щодо значення у відповідному сегментному регістрі.

Синтаксичне опис сегмента на асемблері є конструкцією, зображену малюнку 17:

Рис. 17. Синтаксис опису сегмента

Важливо, що функціональне призначення сегмента дещо ширше, ніж просте розбиття програми на блоки коду, даних та стека. p align="justify"> Сегментація є частиною більш загального механізму, пов'язаного з концепцією модульного програмування. Вона передбачає уніфікацію оформлення об'єктних модулів, створюваних компілятором, зокрема з різних мов програмування. Це дозволяє об'єднувати програми, написані різними мовами. Саме для реалізації різних варіантів такого об'єднання призначені операнди в директиві SEGMENT.

Розглянемо їх докладніше.

1. Атрибут вирівнювання сегмента (тип вирівнювання) повідомляє компонувальнику про те, що необхідно забезпечити розміщення початку сегмента на заданому кордоні. Це важливо, оскільки при правильному вирівнюванні доступ до даних процесорів i80x86 виконується швидше. Допустимі значення цього атрибуту такі:

1) BYTE – вирівнювання не виконується. Сегмент може починатися з будь-якої адреси пам'яті;

2) WORD - сегмент починається за адресою, кратною двом, тобто останній (молодший) значний біт фізичної адреси дорівнює 0 (вирівнювання на межу слова);

3) DWORD - сегмент починається за адресою, кратною чотирьом, тобто два останніх (молодших) значущих біта дорівнюють 0 (вирівнювання на межу подвійного слова);

4) PARA - сегмент починається за адресою, кратною 16, тобто остання шістнадцяткова цифра адреси повинна бути Oh (вирівнювання на межу параграфа);

5) PAGE - сегмент починається за адресою, кратною 256, тобто дві останні шістнадцяткові цифри повинні бути 00h (вирівнювання на кордон 256-байтної сторінки);

6) MEMPAGE - сегмент починається за адресою, кратною 4 Кбайт, тобто три останні шістнадцяткові цифри повинні бути OOOh (адреса наступної 4-Кбайтної сторінки пам'яті). Тип вирівнювання має значення PARA.

2. Атрибут комбінування сегментів (комбінаторний тип) повідомляє компонувальнику, як потрібно комбінувати сегменти різних модулів, що мають одне й те саме ім'я. Значення атрибуту комбінування сегмента можуть бути:

1) PRIVATE - сегмент не об'єднуватиметься з іншими сегментами з тим же ім'ям поза цим модулем;

2) PUBLIC – змушує компонувальник з'єднати всі сегменти з однаковими іменами. Новий об'єднаний сегмент буде цілим та безперервним. Усі адреси (зміщення) об'єктів, а це можуть бути залежно від типу сегмента команди та дані, будуть обчислюватися щодо початку цього нового сегмента;

3) COMMON - має у своєму розпорядженні всі сегменти з одним і тим же ім'ям за однією адресою. Усі сегменти з цим ім'ям будуть перекриватися та спільно використовувати пам'ять. Розмір отриманого в результаті сегмента дорівнюватиме розміру найбільшого сегмента;

4) AT хххх - має сегмент за абсолютною адресою параграфа (параграф - обсяг пам'яті, кратний 16; тому остання шістнадцяткова цифра адреси параграфа дорівнює 0). Абсолютна адреса параграфа задається виразом ххх. Компонувальник має сегмент за заданою адресою пам'яті (це можна використовувати, наприклад, для доступу до відеопам'яті або області ПЗ>), враховуючи атрибут комбінування. Фізично це означає, що сегмент при завантаженні в пам'ять буде розташований, починаючи з абсолютної адреси параграфа, але для доступу до нього у відповідний сегментний регістр має бути завантажене задане в атрибуті значення. Усі мітки та адреси у визначеному таким чином сегменті відраховуються щодо заданої абсолютної адреси;

5) STACK – визначення сегмента стека. Примушує компонувальник з'єднати всі однойменні сегменти та обчислювати адреси цих сегментах щодо регістру ss. Комбінований тип STACK (стек) аналогічний комбінованого типу PUBLIC, за винятком того, що регістр ss є стандартним сегментним регістром для сегментів стека. Регістр sp встановлюється на кінець об'єднаного сегмента стека. Якщо не вказано жодного сегмента стека, компонувальник видасть попередження, що стековий сегмент не знайдено. Якщо сегмент стека створений, а комбінований тип STACK не використовується, програміст повинен завантажити в регістр ss адресу сегмента (подібно до того, як це робиться для регістру ds).

За умовчанням атрибут комбінування набуває значення PRIVATE.

3. Атрибут класу сегмента (тип класу) – це укладений у лапки рядок, що допомагає компонувальнику визначити відповідний порядок прямування сегментів при збиранні програми із сегментів кількох модулів. Компонувальник об'єднує разом у пам'яті всі сегменти з тим самим ім'ям класу (ім'я класу у випадку може бути будь-яким, але краще, якщо воно відбиватиме функціональне призначення сегмента). Типовим прикладом використання імені класу є об'єднання у групу всіх сегментів коду програми (зазвичай при цьому використовується клас "code"). За допомогою механізму типізації класу можна групувати також сегменти ініціалізованих та неініціалізованих даних.

4. Атрибут розміру сегмента. Для процесорів i80386 і вище сегменти можуть бути 16 або 32-розрядними. Це впливає насамперед на розмір сегмента та порядок формування фізичної адреси всередині нього. Атрибут може приймати такі значення:

1) USE16 - це означає, що сегмент допускає 16-розрядну адресацію. При формуванні фізичної адреси може використовуватися лише 16-розрядне усунення. Відповідно такий сегмент може містити до 64 Кбайт коду або даних;

2) USE32 - сегмент буде 32-розрядним. При формуванні фізичної адреси може використовуватися 32-розрядне усунення. Тому такий сегмент може містити до 4 Гб коду або даних.

Усі сегменти власними силами рівноправні, оскільки директиви SEGMENT і ENDS містять інформації про функціональне призначення сегментів. Для того щоб використовувати їх як сегменти коду, даних або стека, необхідно попередньо повідомити транслятор про це, для чого використовують спеціальну директиву ASSUME, що має формат, показаний на рис. 18. Ця директива повідомляє транслятору про те, який сегмент до якого сегментного регістру прив'язаний. Це дозволить транслятору коректно пов'язувати символічні імена, визначені в сегментах. Прив'язка сегментів до сегментних регістрів здійснюється за допомогою операндів цієї директиви, в яких ім'я_сегменту має бути ім'ям сегмента, визначеним у вихідному тексті програми директивою SEGMENT або ключовим словом nothing. Якщо як операнда використовується лише ключове слово nothing, то попередні призначення сегментних регістрів анулюються, причому відразу для всіх шести сегментних регістрів. Але ключове слово nothing можна використовувати замість аргументу назву сегмента; у цьому випадку буде вибірково розриватися зв'язок між сегментом з ім'ям сегмента і відповідним сегментним регістром (див. рис. 18).

Рис. 18. Директива ASSUME

Для простих програм, що містять один сегмент для коду, даних і стека, хотілося б спростити її опис. Для цього транслятори MASM і TASM ввели можливість використання спрощених директив сегментації. Але тут виникла проблема, пов'язана з тим, що необхідно було якось компенсувати неможливість безпосередньо керувати розміщенням та комбінуванням сегментів. Для цього спільно зі спрощеними директивами сегментації стали використовувати директиву вказівки моделі пам'яті MODEL, яка частково почала керувати розміщенням сегментів та виконувати функції директиви ASSUME (тому при використанні спрощених директив сегментації директиву ASSUME можна не використовувати). Ця директива пов'язує сегменти, які у разі використання спрощених директив сегментації мають зумовлені імена, із сегментними регістрам (хоча явно ініціалізувати ds все одно доведеться).

Синтаксис директиви MODEL показаний малюнку 19.

Рис. 19. Синтаксис директиви MODEL

Обов'язковим параметром директиви є модель пам'яті. Цей параметр визначає модель сегментації пам'яті програмного модуля. Передбачається, що програмний модуль може мати лише певні типи сегментів, які визначаються згаданими раніше спрощеними директивами опису сегментів. Ці директиви наведені у таблиці 5.

Таблиця 5. Спрощені директиви визначення сегмента

Наявність у деяких директивах параметра [ім'я] говорить про те, що можливе визначення кількох сегментів цього типу. З іншого боку, наявність кількох видів сегментів даних зумовлено вимогою забезпечити сумісність з деякими компіляторами мов високого рівня, які створюють різні сегменти даних для ініціалізованих та неініціалізованих даних, а також констант.

При використанні директиви MODEL транслятор робить доступними кілька ідентифікаторів, до яких можна звертатися під час роботи програми, щоб отримати інформацію про ті чи інші характеристики цієї моделі пам'яті (табл. 7). Перерахуємо ці ідентифікатори та їх значення (табл. 6).

Таблиця 6. Ідентифікатори, які створюються директивою MODEL

Тепер можна закінчити обговорення директиви MODEL. Операнди директиви MODEL використовують для завдання моделі пам'яті, яка визначає набір сегментів програми, розміри сегментів даних та коду, спосіб зв'язування сегментів та сегментних регістрів. У таблиці 7 наведено деякі значення параметра "модель пам'яті" директиви MODEL.

Таблиця 7. Моделі пам'яті

Параметр модифікатора директиви MODEL дозволяє уточнити деякі особливості використання обраної моделі пам'яті (табл. 8).

Таблиця 8. Модифікатори моделі пам'яті

Необов'язкові параметри "мова" та "модифікатор мови" визначають деякі особливості виклику процедур. Необхідність використання цих параметрів з'являється при написанні та зв'язуванні програм різними мовами програмування.

Описані нами стандартні та спрощені директиви сегментації не виключають один одного. Стандартні директиви використовуються, коли програміст бажає отримати повний контроль за розміщенням сегментів у пам'яті та їх комбінуванням з сегментами інших модулів.

Спрощені директиви доцільно використовувати для простих програм та програм, призначених для зв'язування з програмними модулями, написаними мовами високого рівня. Це дозволяє компонувальнику ефективно пов'язувати модулі різних мов за рахунок стандартизації зв'язків та управління.

ЛЕКЦІЯ № 17. Структури команд на асемблері

1. Структура машинної команди

Машинна команда є закодована за певними правилами вказівка ​​мікропроцесору на виконання деякої операції або дії. Кожна команда містить елементи, що визначають:

1) що робити? (Відповідь це питання дає елемент команди, званий кодом операції (КОП).);

2) об'єкти, з яких треба щось робити (ці елементи називаються операндами);

3) як робити? (Ці елементи називаються типами операнда - зазвичай задаються неявно).

Наведений малюнку 20 формат машинної команди є найзагальнішим. Максимальна довжина машинної команди – 15 байт. Реальна команда може містити набагато меншу кількість полів, аж до одного – лише КОП.

Рис. 20. Формат машинної команди

Опишемо призначення полів машинної команди.

1. Префікси.

Необов'язкові елементи машинної команди, кожен з яких складається з 1 байта або може бути відсутнім. У пам'яті префікси передують команді. Призначення префіксів - модифікувати операцію, яку виконує команда. Прикладна програма може використовувати такі типи префіксів:

1) префікс заміни сегмента. У явній формі вказує, який сегментний регістр використовується в даній команді для адресації стека чи даних. Префікс скасовує вибір сегментного регістру за промовчанням. Префікси заміни сегмента мають такі значення:

а) 2eh – заміна сегмента cs;

б) 36h – заміна сегмента ss;

в) 3eh – заміна сегмента ds;

г) 26h – заміна сегмента es;

д) 64h – заміна сегмента fs;

е) 65h – заміна сегмента gs;

2) префікс розрядності адреси уточнює розрядність адреси (32- або 16-розрядна). Кожній команді, в якій використовується адресний операнд, ставиться у відповідність розрядність адреси цього операнда. Ця адреса може мати розрядність 16 чи 32 біт. Якщо розрядність адреси для цієї команди 16 біт, це означає, що команда містить 16-розрядне зміщення (рис. 20), воно відповідає 16-розрядному зміщення адресного операнда щодо початку деякого сегмента. У контексті малюнка 21 це усунення називається ефективний адресу. Якщо розрядність адреси 32 біт, це означає, що команда містить 32-розрядне зміщення (рис. 20), воно відповідає 32-розрядному зсуву адресного операнда щодо початку сегмента, і за його значенням формується 32-розрядне зміщення в сегменті. За допомогою префікса розрядності адреси можна змінити значення розрядності адреси, що діє за умовчанням. Ця зміна стосуватиметься лише тієї команди, якій передує префікс;

Рис. 21. Механізм формування фізичної адреси у реальному режимі

3) префікс розрядності операнда аналогічний префіксу розрядності адреси, але вказує на розрядність операндів (32- або 16-розрядні), з якими працює команда. Відповідно до яких правил встановлюються значення атрибутів розрядності адреси та операндів за замовчуванням?

У реальному режимі та режимі віртуального значення 18086 цих атрибутів - 16 біт. У захищеному режимі значення атрибутів залежить від стану біта D в дескрипторах виконуваних сегментів. Якщо D = 0, значення атрибутів, що діють за замовчуванням, дорівнюють 16 біт; якщо D = 1, то 32 біти.

Значення префіксів розрядності операнда 66h та розрядності адреси 67h. За допомогою префікса розрядності адреси в реальному режимі можна використовувати 32-розрядну адресацію, але необхідно пам'ятати про обмеженість розміру сегмента величиною 64 Кбайт. Аналогічно префіксу розрядності адреси можна використовувати префікс розрядності операнда у реальному режимі до роботи з 32-разрядными операндами (наприклад, в арифметичних командах);

4) префікс повторення використовується з ланцюжковими командами (командами обробки рядків). Цей префікс "зациклює" команду для обробки всіх елементів ланцюжка. Система команд підтримує два типи префіксів:

а) безумовні (rep - OOh), що змушують повторюватися ланцюжкову команду кілька разів;

б) умовні (repe/repz – OOh, repne/repnz – 0f2h), які при зациклюванні перевіряють деякі прапори, і в результаті перевірки можливий достроковий вихід із циклу.

2. Код операції.

Обов'язковий елемент, що описує операцію, яку виконує команда. Багатьом командам відповідає кілька кодів операцій, кожен із яких визначає нюанси виконання операції. Наступні поля машинної команди визначають місце розташування операндів, що беруть участь в операції, та особливості їх використання. Розгляд цих полів пов'язаний із способами завдання операндів у машинній команді і тому буде виконано пізніше.

3. Байт режиму адресації modr/m.

Значення цього байта визначає форму адреси операндів, що використовується. Операнди можуть бути в пам'яті в одному або двох регістрах. Якщо операнд знаходиться в пам'яті, то байт modr/m визначає компоненти (зсув, базовий та індексний регістри), які використовуються для обчислення ефективної адреси (рисунок 21). У захищеному режимі визначення місця розташування операнда у пам'яті може додатково використовуватися байт sib (Scale-Index-Base - масштаб-індекс-база). Байт modr/m складається із трьох полів (рис. 20):

1) поле mod визначає кількість байт, що займаються в команді адресою операнда (рис. 20, поле усунення в команді). Поле mod використовується разом із полем r/m, яке вказує спосіб модифікації адреси операнда "зміщення у команді". Наприклад, якщо mod = 00, це означає, що зсув у команді відсутня, і адреса операнда визначається вмістом базового і (або) індексного регістру. Які саме регістри будуть використовуватись для обчислення ефективної адреси, визначається значенням цього байта. Якщо mod = 01, це означає, що поле усунення в команді присутнє, займає 1 байт і модифікується вмістом базового та (або) індексного регістру. Якщо mod = 10, це означає, що поле зміщення в команді присутнє, займає 2 або 4 байти (залежно від чинного за замовчуванням або розміру адреси, що визначається префіксом) і модифікується вмістом базового і (або) індексного регістру. Якщо mod =11, це означає, що операндів у пам'яті немає: вони у регістрах. Це значення байта mod використовується у разі, як у команді застосовується безпосередній операнд;

2) поле reg/коп визначає або регістр, що у команді дома першого операнда, або можливе розширення коду операції;

3) поле r/m використовується спільно з полем mod і визначає або регістр, що знаходиться в команді на місці першого операнда (якщо mod =11), або використовувані для обчислення ефективної адреси (спільно з полем усунення в команді) базові та індексні регістри.

4. Байт масштаб – індекс – база (байт sib).

Використовується для розширення можливостей адресації операндів. На наявність байта sib у машинній команді вказує поєднання одного із значень 01 або 10 поля mod та значення поля r/m = 100. Байт sib складається з трьох полів:

1) поля масштабу ss. У цьому полі розміщується масштабний множник для індексного компонента index, що займає наступні 3 біти байта sib. У полі ss може бути одне з наступних значень: 1, 2, 4, 8.

При обчисленні ефективної адреси на це значення збільшуватиметься вміст індексного регістру;

2) поля index. Використовується для зберігання номера індексного регістру, який застосовується для обчислення ефективної адреси операнда;

3) поля base. Використовується для зберігання номера базового регістру, який також застосовується для обчислення ефективної адреси операнда. Як базовий і індексний регістрів можуть використовуватися практично всі регістри загального призначення.

5. Поле усунення у команді.

8-, 16- або 32-розрядне ціле число зі знаком, що є, повністю або частково (з урахуванням вищенаведених міркувань), значення ефективної адреси операнда.

6. Поле безпосереднього операнда.

Необов'язкове поле, що є 8-, 16- або 32-розрядний безпосередній операнд. Наявність цього поля, звісно, ​​відбивається на значенні байта modr/m.

2. Способи завдання операндів команди

Операнд задається неявно на мікропрограмному рівні

У цьому випадку команда не містить операндів. Алгоритм виконання команди використовує деякі об'єкти за замовчуванням (реєстри, прапори в eflags тощо).

Наприклад, команди cli і sti неявно працюють із прапором переривання if у регістрі eflags, а команда xlat неявно звертається до регістру al і рядку в пам'яті за адресою, що визначається парою регістрів ds: bx.

Операнд задається у самій команді (безпосередній операнд)

Операнд перебуває у коді команди, т. е. є частиною. Для зберігання такого операнда у команді виділяється поле довжиною до 32 біт (рисунок 20). Безпосередній операнд може лише другим операндом (джерелом). Операнд-одержувач може бути або у пам'яті, або у регістрі.

Наприклад: mov ax, 0ffffti пересилає в регістрах шістнадцяткову константу ffff. Команда add sum, 2 складає вміст поля на адресу sum з цілим числом 2 і записує результат за місцем першого операнда, тобто в пам'ять.

Операнд знаходиться в одному з регістрів

Реєстрові операнди вказуються іменами регістрів. Як регістри можуть використовуватися:

1) 32-розрядні регістри ЄАХ, ЄВХ, ЕСХ, EDX, ESI, EDI, ESP, ЄВР;

2) 16-розрядні регістри АХ, BX, СГ, DX, SI, DI, SP, ВР;

3) 8-розрядні регістри АН, AL, ВН, BL, СН, CL, DH, DL;

4) сегментні регістри CS, DS, SS, ES, FS, GS.

Наприклад, команда add ax, bx складає вміст регістрів ах і bх і записує результат bх. Команда dec si зменшує вміст si на 1.

Операнд знаходиться в пам'яті

Це найбільш складний і в той же час найбільш гнучкий спосіб завдання операндів. Він дозволяє реалізувати такі два основні види адресації: пряму та непряму.

У свою чергу непряма адресація має такі різновиди:

1) непряму базову адресацію; інша її назва – регістрова непряма адресація;

2) непряму базову адресацію зі зміщенням;

3) непряму індексну адресацію зі зміщенням;

4) непряму базову індексну адресацію;

5) непряму базову індексну адресацію зі зміщенням.

Операндом є порт введення/виводу

Крім адресного простору оперативної пам'яті, мікропроцесор підтримує адресний простір вводу-виводу, який використовується для доступу до пристроїв введення-виводу. Обсяг адресного простору введення-виведення становить 64 Кбайт. Для будь-якого пристрою комп'ютера у цьому просторі виділяються адреси. Конкретне значення адреси не більше цього простору називається портом ввода-вывода. Фізично порту введення-виведення відповідає апаратний регістр (не плутати з регістром мікропроцесора), доступ до якого здійснюється за допомогою спеціальних команд асемблера in і out.

Наприклад:

in al, 60h; ввести байт із порту 60h

Регістри, адресовані з допомогою порту вводу-виводу, можуть мати розрядність 8,16 або 32 біт, але для конкретного порту розрядність регістра фіксована. Команди in і out працюють із фіксованою номенклатурою об'єктів. Як джерело інформації чи одержувача застосовуються звані регістри-акумулятори ЕАХ, АХ, AL. Вибір регістру визначається розрядністю порту. Номер порту може задаватися безпосереднім операндом у командах in і out чи значенням регістрі DX. Останній спосіб дозволяє динамічно визначити номер порту у програмі.

Операнд знаходиться у стеку

Команди можуть зовсім не мати операнда, мати один або два операнда. Більшість команд вимагають двох операндів, одна з яких є операндом-джерелом, а друга - операндом призначення. Важливо те, що один операнд може розташовуватися в регістрі або пам'яті, а другий операнд обов'язково повинен знаходитися в регістрі або безпосередньо в команді. Безпосередній операнд може бути лише операндом-джерелом. У двооперандній машинній команді можливі такі поєднання операндів:

1) регістр – регістр;

2) регістр – пам'ять;

3) пам'ять – регістр;

4) безпосередній операнд – регістр;

5) безпосередній операнд – пам'ять.

Дане правило має винятки, які стосуються:

1) команд роботи з ланцюжками, які можуть переміщати дані з пам'яті на згадку;

2) команд роботи зі стеком, які можуть переносити дані з пам'яті в стек, що також перебуває у пам'яті;

3) команд типу множення, які, крім операнда, вказаного у команді, використовують ще й другий, неявний операнд.

З перерахованих поєднань операндов найчастіше використовуються регістр - пам'ять і - регістр. Зважаючи на їхню важливість, розглянемо їх докладніше. Обговорення ми супроводжуватимемо прикладами команд асемблера, які показуватимуть, як змінюється формат команди асемблера при застосуванні того чи іншого виду адресації. У зв'язку з цим перегляньте ще раз на малюнку 21, на якому показаний принцип формування фізичної адреси на адресній шині мікропроцесора. Видно, що адреса операнда формується як сума двох складових - зсунутого на 4 біта вмісту сегментного регістру та 16-бітного ефективного адреси, який у загальному випадку обчислюється як сума трьох компонентів: бази, зміщення та індексу.

3. Способи адресації

Перерахуємо і потім розглянемо особливості основних видів адресації операндів у пам'яті:

1) пряму адресацію;

2) непряму базову (реєстрову) адресацію;

3) непряму базову (реєстрову) адресацію зі зміщенням;

4) непряму індексну адресацію зі зміщенням;

5) непряму базову індексну адресацію;

6) непряму базову індексну адресацію зі зміщенням.

Пряма адресація

Це найпростіший вид адресації операнда у пам'яті, оскільки ефективна адреса міститься у самій команді та її формування немає ніяких додаткових джерел чи регістрів. Ефективна адреса береться безпосередньо з поля усунення машинної команди (див. рис. 20), яке може мати розмір 8, 16, 32 біт. Це значення однозначно визначає байт, слово чи подвійне слово, розташовані в сегменті даних.

Пряма адресація може бути двох типів.

Відносна пряма адресація

Використовується для команд умовних переходів для вказівки відносної адреси переходу. Відносність такого переходу полягає в тому, що в полі зміщення машинної команди міститься 8-, 16- або 32-бітове значення, яке в результаті роботи команди складатиметься з вмістом регістру покажчика команд ip/eip. Через війну такого додавання виходить адресу, яким і здійснюється перехід.

Абсолютна пряма адресація

У цьому випадку ефективна адреса є частиною машинної команди, але формується ця адреса тільки значення поля зміщення в команді. Для формування фізичної адреси операнда в пам'яті мікропроцесор складає поле зі зрушеним на 4 біта значенням сегментного регістру. У команді асемблера можна використовувати кілька форм такої адресації.

Але така адресація застосовується рідко - осередкам, що зазвичай використовуються, в програмі присвоюються символічні імена. У процесі трансляції асемблер обчислює і підставляє значення зміщень цих імен в машинну команду, що формується ним, у полі "зміщення в команді". У результаті виходить так, що машинна команда прямо адресує свій операнд, маючи фактично в одному зі своїх полів значення ефективної адреси.

Інші види адресації відносяться до непрямих. Слово " непрямий " у назві цих видів адресації означає те, що у самій команді може лише частина ефективного адреси, інші його компоненти перебувають у регістрах, куди вказують своїм вмістом байт modr/m і, можливо, байт sib.

Непряма базова (реєстрова) адресація

При такій адресації ефективна адреса операнда може знаходитись у будь-якому з регістрів загального призначення, крім sp/esp та bp/ebp (це специфічні регістри для роботи з сегментом стека). Синтаксично у команді цей режим адресації виражається укладанням імені регістру в квадратні дужки []. Наприклад, команда mov ах, [есх] поміщає в регістрах вміст слова за адресою з сегмента даних зі зсувом, що зберігається в регістрі есх. Оскільки вміст регістру легко змінити під час роботи програми, цей спосіб адресації дозволяє динамічно призначити адресу операнда деякої машинної команди. Це властивість дуже корисно, наприклад, в організацію циклічних обчислень й у роботи з різними структурами даних типу таблиць чи масивів.

Непряма базова (реєстрова) адресація зі зміщенням

Цей вид адресації є доповненням попереднього та призначений для доступу до даних з відомим зміщенням щодо деякої базової адреси. Цей вид адресації зручно використовуватиме доступу до елементів структур даних, коли зміщення елементів відомо заздалегідь, на стадії розробки програми, а базова (початкова) адреса структури має обчислюватися динамічно, на стадії виконання програми. Модифікація вмісту базового регістру дозволяє звернутися до однойменних елементів різних екземплярів однотипних структур даних.

Наприклад, команда mov ax, [edx + 3h] пересилає в регістрах слова з області пам'яті за адресою: вміст edx + 3h.

Команда mov ax,mas[dx] пересилає в регістрах слово за адресою: вміст dx плюс значення ідентифікатора mas (не забувайте, що транслятор надає кожному ідентифікатору значення, що дорівнює зміщенню цього ідентифікатора щодо початку сегмента даних).

Непряма індексна адресація зі зміщенням

Цей вид адресації дуже схожий на непряму базову адресацію зі зміщенням. Тут також для формування ефективної адреси використовується один із регістрів загального призначення. Але індексна адресація має одну цікаву особливість, яка дуже зручна для роботи з масивами. Вона пов'язана з можливістю так званого масштабування вмісту індексного регістру. Що це таке?

Подивіться малюнок 20. Нас цікавить байт sib. Під час обговорення структури цього байта ми зазначали, що він складається із трьох полів. Одне з цих полів - поле масштабу ss, значення якого множиться вміст індексного регістру.

Наприклад, команді mov ax,mas[si*2] значення ефективного адреси другого операнда обчислюється виразом mas+(si)*2. У зв'язку з тим, що в асемблері немає коштів для організації індексації масивів, то програмісту самотужки доводиться її організовувати.

Наявність можливості масштабування суттєво допомагає у вирішенні цієї проблеми, але за умови, що розмір елементів масиву становить 1, 2, 4 чи 8 байт.

Непряма базова індексна адресація

При цьому виді адресації ефективна адреса формується як сума вмісту двох регістрів загального призначення: базового та індексного. Як ці регістри можуть застосовуватися будь-які регістри загального призначення, при цьому часто використовується масштабування вмісту індексного регістру.

Непряма базова індексна адресація зі зміщенням

Цей вид адресації є доповненням непрямої індексної адресації. Ефективна адреса формується як сума трьох складових: вмісту базового регістру, вмісту індексного регістру та значення поля усунення в команді.

Наприклад, команда mov eax, [esi + 5] [edx] пересилає в регістр еах подвійне слово за адресою: (esi) + 5 + (edx).

Команда add ax,array[esi] [ebx] здійснює додавання вмісту регістру ах із вмістом слова за адресою: значення ідентифікатора array + (esi) + (ebx).

ЛЕКЦІЯ №18. Команди

1. Команди пересилання даних

Для зручності практичного застосування та відображення їх специфіки команди цієї групи зручніше розглядати відповідно до їх функціонального призначення, згідно з яким їх можна розбити на наступні групи команд:

1) пересилання даних загального призначення;

2) введення-виведення в порт;

3) роботи з адресами та покажчиками;

4) перетворення даних;

5) роботи зі стеком.

Команди пересилання даних загального призначення

До цієї групи належать такі команди:

1) mov – це основна команда пересилання даних. Вона реалізує найрізноманітніші варіанти пересилки. Відзначимо особливості застосування цієї команди:

а) командою mov не можна пересилати з однієї області пам'яті в іншу. Якщо така необхідність виникає, то потрібно використовувати як проміжний буфер будь-який доступний в даний момент регістр загального призначення;

б) не можна завантажити в сегментний регістр значення безпосередньо з пам'яті. Тому для такого завантаження потрібно використовувати проміжний об'єкт. Це може бути регістр загального призначення чи стек;

в) не можна переслати вміст одного сегментного регістру до іншого сегментного регістру. Це тим, що у системі команд немає відповідного коду операції. Але потреба у такій дії часто виникає. Виконати таке пересилання можна, використовуючи як проміжні ті самі регістри загального призначення;

г) не можна використовувати сегментний регістр CS як операнд призначення. Причина тут проста. Справа в тому, що в архітектурі мікропроцесора пара cs:ip завжди містить адресу команди, яка повинна виконуватися наступною. Зміна командою mov вмісту регістру CS фактично означало б операцію переходу, а чи не пересилання, що неприпустимо. 2) xchg - застосовують для двонаправленого пересилання даних. Для цієї операції можна, звичайно, застосувати послідовність кількох команд mov, але через те, що операція обміну використовується досить часто, розробники системи команд мікропроцесора вважали за потрібне ввести окрему команду обміну xchg. Природно, що операнди повинні мати один тип. Не допускається (як і всіх команд асемблера) обмінювати між собою вміст двох осередків пам'яті.

Команди введення-виведення в порт

Подивіться малюнок 22. На ньому показано сильно спрощена, концептуальна схема управління обладнанням комп'ютера.

Рис. 22. Концептуальна схема керування обладнанням комп'ютера

Як видно з малюнка 22, нижнім рівнем є рівень BIOS, на якому робота з обладнанням ведеться безпосередньо через порти. Тим самим було реалізується концепція незалежності від устаткування. При заміні обладнання необхідно буде лише підправити відповідні функції BIOS, переорієнтувавши їх на нові адреси та логіку роботи портів.

Важливо керувати пристроями безпосередньо через порти нескладно. Відомості про номери портів, їх розрядність, формат керуючої інформації наведено в технічному описі пристрою. Необхідно знати лише кінцеву мету своїх дій, алгоритм, відповідно до якого працює конкретний пристрій, і порядок програмування його портів, тобто фактично потрібно знати, що і в якій послідовності потрібно послати в порт (при записі в нього) або рахувати з нього (при читанні) і як слід трактувати цю інформацію. Для цього достатньо двох команд, присутніх у системі команд мікропроцесора:

1) in акумулятор, номер_порту - введення в акумулятор із порту з номером номер_порту;

2) out порт, акумулятор – виведення вмісту акумулятора в порт з номером номер_порту.

Команди роботи з адресами та вказівниками пам'яті

При написанні програм на асемблері проводиться інтенсивна робота з адресами операндів, що у пам'яті. Для підтримки такого роду операцій є спеціальна група команд, до якої входять такі команди:

1) lea призначення, джерело – завантаження ефективної адреси;

2) Ids призначення, джерело - завантаження покажчика в регістр сегмента даних ds;

3) les призначення, джерело - завантаження покажчика в регістр додаткового сегмента es;

4) lgs призначення, джерело - завантаження покажчика в регістр додаткового сегмента даних gs;

5) lfs призначення, джерело - завантаження покажчика в регістр додаткового сегмента даних fs;

6) lss призначення, джерело - завантаження покажчика в регістр сегмента стека ss.

Команда lea схожа на команду mov тим, що вона також пересилає. Однак команда lea здійснює пересилання не даних, а ефективної адреси даних (тобто зміщення даних щодо початку сегмента даних) у регістр, вказаний операндом призначення.

Часто для виконання деяких дій у програмі недостатньо знати значення лише ефективної адреси даних, а необхідно мати повний покажчик на дані. Повний покажчик на дані складається із сегментної складової та зміщення. Всі інші команди цієї групи дозволяють отримати в парі регістрів такий повний покажчик на операнд у пам'яті. При цьому ім'я сегментного регістру, який міститься сегментна складова адреси, визначається кодом операції. Відповідно зміщення міститься в регістр загального призначення, зазначений операндом призначення.

Але не все так просто із операндом джерело. Насправді, у команді як джерело не можна вказувати безпосередньо ім'я операнда у пам'яті, який ми б хотіли отримати покажчик. Попередньо необхідно отримати саме значення повного покажчика в певній області пам'яті та вказати в команді отримання повну адресу імені цієї області. Для виконання цієї дії необхідно згадати директиви резервування та ініціалізації пам'яті.

При застосуванні цих директив можливий окремий випадок, коли в полі операндів вказується ім'я іншої директиви визначення даних (фактично ім'я змінної). І тут пам'яті формується адресу цієї змінної. Яка адреса буде сформована (ефективна або повна), залежить від застосовуваної директиви. Якщо це dw, то пам'яті формується лише 16-битное значення ефективного адреси, а якщо dd - у пам'ять записується повний адресу. Розміщення цієї адреси у пам'яті таке: у молодшому слові перебуває усунення, у старшому - 16-бітна сегментна складова адреси.

Наприклад, при організації роботи з ланцюжком символів зручно помістити її початкову адресу в деякий регістр і далі в циклі модифікувати це значення для послідовного доступу до елементів ланцюжка.

Необхідність використання команд отримання повного покажчика даних у пам'яті, т. е. адреси сегмента значення зміщення всередині сегмента, виникає, зокрема, під час роботи з ланцюжками.

Команди перетворення даних

До цієї групи можна віднести безліч команд мікропроцесора, але більшість із них має ті чи інші особливості, які вимагають віднести їх до інших функціональних груп. Тому з усієї сукупності команд мікропроцесора безпосередньо до команд перетворення даних можна віднести лише одну команду: xlat [адреса_таблиці_перекодування]

Це дуже цікава та корисна команда. Її дія полягає в тому, що вона заміщає значення в регістрі al іншим байтом з таблиці в пам'яті, розташованої за адресою, вказаною операндом адреса_таблиці_перекодування.

Слово "таблиця" дуже умовно, по суті, це просто рядок байт. Адреса байта у рядку, яким буде проводитися заміщення вмісту регістру al, визначається сумою (bx) + (al), тобто вміст al виконує роль індексу в байтовому масиві.

Під час роботи з командою xlat зверніть увагу на наступний тонкий момент. Незважаючи на те, що в команді вказується адреса рядка байт, з якої має бути вилучено нове значення, ця адреса повинна бути завантажена (наприклад, за допомогою команди lea) в регістр bх. Таким чином, операнд адрес_таблиці_перекодування насправді не потрібен (необов'язковість операнда показана укладанням його у квадратні дужки). Що стосується рядка байт (таблиці перекодування), то вона є область пам'яті розміром від 1 до 255 байт (діапазон числа без знака в 8-бітному регістрі).

Команди роботи зі стеком

Ця група є набір спеціалізованих команд, орієнтованих на організацію гнучкої та ефективної роботи зі стеком.

Стек - це область пам'яті, що спеціально виділяється для тимчасового зберігання даних програми. Важливість стека визначається тим, що для нього у структурі програми передбачено окремий сегмент. Якщо програміст забув описати сегмент стека у своїй програмі, компонувальник tlink видасть попереджувальне повідомлення.

Для роботи зі стеком призначені три регістри:

1) ss – сегментний регістр стека;

2) sp/esp - регістр покажчика стека;

3) bp/ebp - регістр покажчика бази кадру стека.

Розмір стека залежить від режиму роботи мікропроцесора і обмежується 64 Кбайт (або 4 Гбайт в захищеному режимі).

У кожний момент часу доступний лише один стек, адреса сегмента якого міститься в регістрі SS. Цей стек називається поточним. Для того, щоб звернутися до іншого стеку ("переключити стек"), необхідно завантажити в регістр ss іншу адресу. Регістр SS автоматично використовується процесором для виконання всіх команд, які працюють зі стеком.

Перерахуємо ще деякі особливості роботи зі стеком:

1) запис та читання даних у стеку здійснюється відповідно до принципу LIFO,

2) у міру запису даних у стек останній зростає у бік молодших адрес. Ця особливість закладена в алгоритм команд роботи зі стеком;

3) при використанні регістрів esp/sp і ebp/bp для адресації пам'яті асемблер автоматично вважає, що значення, що містяться в ньому, являють собою зміщення щодо сегментного регістру ss.

У випадку стек організований так, як показано малюнку 23.

Рис. 23. Концептуальна схема організації стека

Для роботи зі стеком призначені регістри SS, ESP/SP та ЄВР/ВР. Ці регістри використовуються комплексно, і кожен із них має своє функціональне призначення.

Регістр ESP/SP завжди свідчить про вершину стека, т. е. містить зміщення, яким у стек був занесений останній елемент. Команди роботи зі стеком неявно змінюють цей регістр так, щоб він завжди вказував на останній записаний у стек елемент. Якщо стек порожній, значення esp дорівнює адресі останнього байта сегмента, виділеного під стек. При занесенні елемента в стек процесор зменшує значення регістру esp, а потім записує елемент на адресу нової вершини. При вийманні даних зі стека процесор копіює елемент, розташований за адресою вершини, а потім збільшує значення регістру покажчика стека esp. Таким чином, виходить, що стек росте вниз, у бік зменшення адрес.

Що робити, якщо нам потрібно отримати доступ до елементів не на вершині, а всередині стека? Для цього застосовують регістр ЄВР Регістр ЄВР - регістр покажчика бази кадру стека.

Наприклад, типовим прийомом при вході в підпрограму є передача потрібних параметрів шляхом запису в стек. Якщо підпрограма теж активно працює зі стеком, доступ до цих параметрів стає проблематичним. Вихід у тому, щоб після запису потрібних даних у стек зберегти адресу вершини стека в покажчику кадру (бази) стека - регістрі ЄВР. Значення в ЄВР можна використовувати для доступу до переданих параметрів.

Початок стека розташований у старших адресах пам'яті. На малюнку 23 ця адреса позначена парою ss: fffF. Усунення шТ наведено тут умовно. Реально це значення визначається величиною, яку програміст задає при описі сегмента стека у своїй програмі.

Для організації роботи зі стеком існують спеціальні команди запису та читання.

1. push джерело - запис значення джерело на вершину стека.

Інтерес є алгоритмом роботи цієї команди, який включає наступні дії (рис. 24):

1) (sp) = (sp) - 2; значення sp зменшується на 2;

2) значення джерела записується за адресою, що вказується парою ss: sp.

Рис. 24. Принцип роботи команди

2. pop призначення - запис значення з вершини стека за місцем, вказаним операндом призначення. Значення у своїй " знімається " з вершини стека. Алгоритм роботи команди pop обернений алгоритмом команди push (рис. 25):

1) запис вмісту вершини стека за місцем, зазначеним операндом призначення;

2) (sp) = (sp) + 2; збільшення значення sp.

Рис. 25. Принцип роботи команди pop

3. pusha – команда групового запису в стек. По цій команді в стек послідовно записуються регістри ах, сх, dx, bx, sp, bp, si, di. Зауважимо, що записується оригінальний вміст sp, тобто той, який був до видачі команди pusha (рис. 26).

Рис. 26. Принцип роботи команди pusha

4. pushaw - майже синонім команди pusha У чому різниця? Атрибут розрядності може набувати значення use16 або use32. Розглянемо роботу команд pusha і pushaw при кожному з цих атрибутів:

1) use16 - алгоритм роботи pushaw аналогічний алгоритму pusha;

2) use32 - pushaw не змінюється (тобто вона нечутлива до розрядності сегмента і завжди працює з регістрами розміром у слово - ах, сх, dx, bx, sp, bp, si, di). Команда pusha чутлива до встановленої розрядності сегмента і при вказівці 32-розрядного сегмента працює з відповідними 32-розрядними регістрів, тобто еах, есх, edx, ebx, esp, ebp, esi, edi.

5. pushad - виконується аналогічно до команди pusha, але є деякі особливості.

Наступні три команди виконують дії, обернені до вищеописаних команд:

1) рора;

2) popaw;

3) popad.

Група команд, описана нижче, дозволяє зберегти в стеку регістр прапорів та записати слово чи подвійне слово у стеку. Зазначимо, що наведені нижче команди - єдині у системі команд мікропроцесора, які дозволяють отримати доступ (і які потребують цього доступу) до всього вмісту регістру прапорів.

1. pushf – зберігає регістр прапорів у стеку.

Робота цієї команди залежить від атрибуту розміру сегмента:

1) use 16 - в стек записується регістр flags розміром 2 байти;

2) use32 - в стек записується регістр eflags розміром 4 байти.

2. pushfw - збереження у стеку регістру прапорів розміром слово. Завжди працює як pushf з атрибутом use16.

3. pushfd - збереження в стеку регістру прапорів flags чи eflags залежно від атрибуту розрядності сегмента (тобто, що й pushf).

Аналогічно, наступні три команди виконують дії, обернені до розглянутих вище операцій:

1) popf;

2) popftv;

3) popfd.

І на закінчення відзначимо основні види операції, коли використання стека практично неминуче:

1) виклик підпрограм;

2) тимчасове збереження значень регістрів;

3) визначення локальних змінних.

2. Арифметичні команди

Мікропроцесор може виконувати цілочисленні операції та операції з плаваючою точкою. Для цього в його архітектурі є два окремі блоки:

1) пристрій для виконання цілих операцій;

2) пристрій для виконання операцій із плаваючою точкою.

Кожен із цих пристроїв має свою систему команд. В принципі, цілісний пристрій може взяти на себе багато функцій пристрою з плаваючою точкою, але це вимагатиме великих обчислювальних витрат. Для більшості завдань, які використовують мову асемблера, достатньо цілої арифметики.

Огляд групи арифметичних команд та даних

Цілочисельний обчислювальний пристрій підтримує трохи більше десятка арифметичних команд. На малюнку 27 наведено класифікацію команд цієї групи.

Рис. 27. Класифікація арифметичних команд

Група арифметичних цілих команд працює з двома типами чисел:

1) цілими двійковими числами. Числа можуть мати знаковий розряд або не мати такого, тобто бути числами зі знаком або знака;

2) цілими десятковими числами.

Розглянемо машинні формати, де зберігаються ці типи даних.

Цілі двійкові числа

Ціле двійкове число з фіксованою точкою – це число, закодоване у двійковій системі числення.

Розмірність цілого двійкового числа може становити 8, 16 чи 32 біт. Знак двійкового числа визначається тим, як інтерпретується старший біт у поданні числа. Це 7,15 або 31 біти для чисел відповідної розмірності. При цьому цікаво те, що серед арифметичних команд є лише дві команди, які дійсно враховують цей старший розряд як знаковий, - це команди цілісного множення та поділу imul та idiv. В інших випадках відповідальність за дії зі знаковими числами і, відповідно, із знаковим розрядом лягає на програміста. Діапазон значень двійкового числа залежить від його розміру і трактування старшого біта або як старшого біта числа, або як біта знака числа (табл. 9).

Таблиця 9. Діапазон значень двійкових чиселДесятичні числа

Десяткові числа - спеціальний вид подання числової інформації, основою якого покладено принцип кодування кожної десяткової цифри числа групою з чотирьох біт. При цьому кожен байт числа містить одну або дві десяткові цифри у так званому двійково-десятковому коді (BCD – Binary-Coded Decimal). Мікропроцесор зберігає BCD-числа у двох форматах (рис. 28):

1) упакований формат. У цьому форматі кожен байт містить дві десяткові цифри. Десяткова цифра є двійкове значення в діапазоні від 0 до 9 розміром 4 біта. При цьому код старшої цифри числа займає 4 біти старші. Отже, діапазон уявлення десяткового упакованого числа в 1 байті становить від 00 до 99;

2) невпакований формат. У цьому форматі кожен байт містить одну десяткову цифру у чотирьох молодших бітах. Старші 4 біти мають нульове значення. Це так звана зона. Отже, діапазон уявлення десяткового невпакованого числа в 1 байті становить від 0 до 9.

Рис. 28. Подання BCD-чисел

Як описати двійково-десяткові числа у програмі? Для цього можна використовувати лише дві директиви опису та ініціалізації даних – db та dt. Можливість застосування лише цих директив для опису BCD-чисел обумовлена ​​тим, що до таких чисел застосовується принцип "молодший байт за молодшою ​​адресою", що дуже зручно для їх обробки. І взагалі, при використанні такого типу даних як BCD-числа, порядок опису цих чисел у програмі та алгоритм їх обробки – це справа смаку та особистих уподобань програміста. Це стане ясно після того, як ми розглянемо нижче основи роботи з BCD-числами.

Арифметичні операції над цілими двійковими числами

Додавання двійкових чисел без знака

Мікропроцесор виконує складання операндів за правилами складання двійкових чисел. Проблем не виникає доти, доки значення результату не перевищує розмірності поля операнда. Наприклад, при складанні операндів розміром байт результат не повинен перевищувати число 255. Якщо це відбувається, то результат виявляється невірним. Розглянемо чому так відбувається.

Наприклад, виконаємо додавання: 254 + 5 = 259 у двійковому вигляді. 11111110 + 0000101 = 1. Результат вийшов за межі 00000011 біт і правильне його значення вкладається в 8 біт, а в 9-бітовому полі операнда залишилося значення 8, що, звичайно, неправильно. У мікропроцесорі цей результат додавання прогнозується та передбачені спеціальні засоби для фіксування подібних ситуацій та їх обробки. Так, для фіксування ситуації виходу за розрядну сітку результату, як у цьому випадку, призначений прапор перенесення cf. Він розташований в біту 3 регістра прапорів EFLAGS/FLAGS. Саме встановленням цього прапора фіксується факт перенесення одиниці зі старшого розряду операнда. Звичайно, програміст повинен враховувати можливість такого результату операції складання та передбачати кошти для коригування. Це передбачає включення ділянок коду після операції додавання, в яких аналізується прапор cf. Аналіз цього прапора можна провести у різний спосіб.

Найпростіший і доступніший - використовувати команду умовного переходу jcc. Ця команда як операнда має ім'я мітки у поточному сегменті коду. Перехід на цю мітку здійснюється у разі, якщо в результаті роботи попередньої команди прапор cf встановився в 1. У системі команд мікропроцесора є три команди двійкового додавання:

1) inc операнд - операція інкременту, тобто збільшення значення операнда на 1;

2) add операнд_1, операнд_2 - команда додавання з принципом дії: операнд_1 = операнд_1 + операнд_2;

3) adc операнд_1, операнд_2 - команда додавання з урахуванням прапора перенесення cf. Принцип дії команди: операнд_1 = операнд_1 + операнд_2 + значення_СГ.

Зверніть увагу на останню команду - це команда додавання, що враховує перенесення одиниці зі старшого розряду. Механізм появи такої одиниці ми вже розглянули. Таким чином, команда adc є засобом мікропроцесора для складання довгих двійкових чисел, розмірність яких перевершує довжини стандартних полів, що підтримуються мікропроцесором.

Додавання двійкових чисел зі знаком

Насправді мікропроцесор "не підозрює" про різницю між числами зі знаком і без знака. Натомість у нього є засоби фіксування виникнення характерних ситуацій, що складаються в процесі обчислень. Деякі ми розглянули під час обговорення складання чисел без знака:

1) прапор перенесення cf, установка якого в 1 говорить про те, що відбувся вихід за межі розрядності операндів;

2) команду adc, яка враховує можливість такого виходу (перенесення із молодшого розряду).

Інший засіб - це реєстрація стану старшого (знакового) розряду операнда, яке здійснюється за допомогою прапора переповнення of у регістрі EFLAGS (біт 11).

Ви, звичайно, пам'ятаєте, як видаються числа у комп'ютері: позитивні - у двійковому коді, негативні - у додатковому коді. Розглянемо різні варіанти складання чисел. Приклади покликані показати поведінку двох старших бітів операндів та правильність результату операції складання.

Приклад

30566 = 0111011101100110

+

00687 = 00000010

=

31253 = 01111010

Слідкуємо за переносами з 14 та 15-го розрядів та правильністю результату: переносів немає, результат правильний.

Приклад

30566 = 0111011101100110

+

30566 = 0111011101100110

=

1132 = 11101110

Відбулося перенесення з 14-го розряду; із 15-го розряду перенесення немає. Результат неправильний, оскільки є переповнення - значення числа вийшло більше, ніж те, що може мати 16-бітове число зі знаком (+32).

Приклад

-30566 = 10001000 10011010

+

-04875 = 11101100 11110101

=

-35441 = 01110101 10001111

Відбулося перенесення з 15-го розряду, з 14-го розряду немає перенесення. Результат неправильний, оскільки замість негативного числа вийшло позитивне (у старшому биті 0).

Приклад

-4875 = 11101100 11110101

+

-4875 = 11101100 11110101

=

09750 = 11011001

Є перенесення з 14 та 15-го розрядів. Результат правильний.

Таким чином, ми дослідили всі випадки та з'ясували, що ситуація переповнення (установка прапора OF в 1) відбувається при перенесенні:

1) із 14-го розряду (для позитивних чисел зі знаком);

2) із 15-го розряду (для негативних чисел).

І навпаки, переповнення не відбувається (тобто прапор OF скидається в 0), якщо є перенесення з обох розрядів або відсутнє перенесення в обох розрядах.

Отже, переповнення реєструється за допомогою прапора переповнення of. Додатково до прапора of при переносі зі старшого розряду встановлюється в 1 і прапор переносу CF Оскільки мікропроцесор не знає про існування чисел зі знаком і без знака, то вся відповідальність за правильність дій з числами, що вийшли, лягає на програміста. Проаналізувати прапори CF і OF можна командами умовного переходу JC JNC і JO JNO відповідно.

Що ж до команд складання чисел зі знаком, всі вони самі, що й у чисел без знака.

Віднімання двійкових чисел без знака

Як і при аналізі операції додавання, поміркуємо над суттю процесів, що відбуваються при виконанні операції віднімання. Якщо зменшуване більше віднімається, то проблем немає, - різниця позитивна, результат вірний. Якщо зменшуване менше віднімається, виникає проблема: результат менше 0, а це вже число зі знаком. У цьому випадку результат необхідно загорнути. Що це означає? При звичайному відніманні (у стовпчик) роблять позику 1 зі старшого розряду. Мікропроцесор надходить аналогічно, тобто займає 1 з розряду, що йде за старшим, у розрядній сітці операнда. Пояснимо на прикладі.

Приклад

05 = 00000000

-10 = 00000000 00001010

Для того щоб зробити віднімання, зробимо

уявна позика зі старшого розряду:

100000000 00000101

-

00000000 00001010

=

11111111 11111011

Тим самим, по суті, виконується дія

(65 + 536) - 5 = 10

0 тут хіба що еквівалентний числу 65536. Результат, звісно, ​​неправильний, але мікропроцесор вважає, що це нормально, хоча факт позики одиниці він фіксує установкою прапора переносу cf. Але подивіться ще раз уважно на результат операції віднімання. Це ж -5 у додатковому коді! Проведемо експеримент: представимо різницю у вигляді суми 5+(-10).

Приклад

5 = 00000000

+

(-10) = 11111111 11110110

=

11111111 11111011

тобто ми отримали той самий результат, що й у попередньому прикладі.

Таким чином, після команди віднімання чисел без знака потрібно аналізувати стан прапора РЄ. Якщо він встановлений в 1, то це говорить про те, що відбулася позика зі старшого розряду і результат вийшов у додатковому коді.

Аналогічно командам складання група команд віднімання складається з мінімально можливого набору. Ці команди виконують віднімання за алгоритмами, які ми зараз розглядаємо, а облік особливих ситуацій має проводитись самим програмістом. До команд віднімання належать такі:

1) dec операнд – операція декремента, тобто зменшення значення операнда на 1;

2) sub операнд_1, операнд_2 - команда віднімання; її принцип дії: операнд_1 = операнд_1 – операнд_2;

3) sbb операнд_1, операнд_2 – команда віднімання з урахуванням позики (прапора ci): операнд_1 = операнд_1 – операнд_2 – значення_сГ.

Як бачите, серед команд віднімання є команда sbb, яка враховує прапор перенесення cf. Ця команда подібна до adc, але тепер уже прапор cf виконує роль індикатора позики 1 зі старшого розряду при відніманні чисел.

Віднімання двійкових чисел зі знаком

Тут усе дещо складніше. Мікропроцесору нема чого мати два пристрої - складання та віднімання. Достатньо наявності лише одного - пристрою додавання. Але для віднімання способом додавання чисел зі знаком у додатковому коді необхідно представляти обидва операнда - і зменшуване, і віднімається. Результат теж потрібно розглядати як значення додаткового коду. Але тут виникають складнощі. Насамперед вони пов'язані з тим, що старший біт операнда сприймається як знаковий. Розглянемо приклад віднімання 45 - (-127).

Приклад

Віднімання чисел зі знаком 1

45 = 0010

-

-127 = 1000 0001

=

-44 = 1010 1100

Судячи з знакового розряду, результат вийшов негативний, що, своєю чергою, свідчить, що число треба як доповнення, рівне -44. Правильний результат має дорівнювати 172. Тут ми, як і у разі знакового додавання, зустрілися з переповненням мантиси, коли значний розряд числа змінив знаковий розряд операнда. Відстежити таку ситуацію можна за вмістом прапора переповнення of. Його установка в 1 говорить про те, що результат вийшов за діапазон уявлення знакових чисел (тобто змінився старший біт) для операнда даного розміру, і програміст повинен передбачити дії коригування результату.

Приклад

Віднімання чисел зі знаком 2

-45-45 = -45 + (-45) = -90.

-45 = 11010011

+

-45 = 11010011

=

-90 = 1010 0110

Тут все нормально, прапор переповнення of скинуто в 0, а 1 у знаковому розряді говорить про те, що значення результату - число додаткового коду.

Віднімання та складання операндів великої розмірності

Якщо ви помітили, команди додавання та віднімання працюють з операндами фіксованої розмірності: 8, 16, 32 біт. А що робити, якщо потрібно скласти числа більшої розмірності, наприклад, 48 біт, використовуючи 16-розрядні операнди? Наприклад, складемо два 48-розрядні числа:

Рис. 29. Додавання операндів великої розмірності

На малюнку 29 по кроках показано технологію складання довгих чисел. Видно, що процес складання багатобайтних чисел відбувається так само, як і при додаванні двох чисел "у стовпчик", - із здійсненням за необхідності перенесення 1 у старший розряд. Якщо нам вдасться запрограмувати цей процес, ми значно розширимо діапазон двійкових чисел, над якими зможемо виконувати операції складання і віднімання.

Принцип віднімання чисел з діапазоном уявлення, що перевищує стандартні розрядні сітки операндів, той же, що і при додаванні, тобто використовується прапор переносу cf. Потрібно тільки уявляти процес віднімання в стовпчик і правильно комбінувати команди мікропроцесора з командою sbb.

На завершення обговорення команд додавання та віднімання зазначимо, що крім прапорів cf та of у регістрі eflags є ще кілька прапорів, які можна використовувати з двійковими арифметичними командами. Йдеться про такі прапори:

1) zf - прапор нуля, який встановлюється в 1, якщо результат операції дорівнює 0, і в 1 якщо результат не дорівнює 0;

2) sf – прапор знака, значення якого після арифметичних операцій (і не тільки) збігається зі значенням старшого біта результату, тобто з бітом 7, 15 або 31. Таким чином, цей прапор можна використовувати для операцій над числами зі знаком.

Збільшення чисел без знака

Для множення чисел без знака призначено команду

mul сомножитель_1

Як бачите, в команді вказано лише один операнд-множник. Другий операнд-множник_2 заданий неявно. Його розташування фіксоване і залежить від розміру співмножників. Так як у загальному випадку результат множення більший, ніж будь-який з його співмножників, його розмір і місцезнаходження повинні бути теж визначені однозначно. Варіанти розмірів співмножників та розміщення другого операнда та результату наведено в таблиці 10.

Таблиця 10. Розташування операндів та результату при множенні

З таблиці видно, що добуток складається з двох частин і в залежності від розміру операндів розміщується у двох місцях - на місці сомножитель_2 (молодша частина) та в додатковому регістрі ah, dx, edx (старша частина). Як динамічно (тобто під час виконання програми) дізнатися, що результат досить малий і вмістився в одному регістрі або що він перевищив розмірність регістру і старша частина опинилася в іншому регістрі? Для цього залучаються вже відомі нам за попереднім обговоренням прапори перенесення cf та переповнення of:

1) якщо старша частина результату нульова, то після операції добутку прапори cf = 0 та of = 0;

2) якщо ці прапори ненульові, це означає, що результат вийшов межі молодшої частини твори і складається з двох частин, що й треба враховувати при подальшій роботі.

Розмноження чисел зі знаком

Для множення чисел зі знаком призначена команда

[imul операнд_1, операнд_2, операнд_3]

Ця команда виконується як і, як і команда mul. Відмінною рисою команди imul є лише формування знака.

Якщо результат малий і уміщається в одному регістрі (тобто якщо cf = of = 0), то вміст іншого регістру (старшої частини) є розширенням знака - всі його біти дорівнюють старшому біту (знаковому розряду) молодшої частини результату. Інакше (якщо cf = of = 1) знаком результату є знаковий біт старшої частини результату, а знаковий біт молодшої частини є значним бітом двійкового коду результату.

Розподіл чисел без знака

Для поділу чисел без знака призначено команду

div дільник

Дільник може перебувати в пам'яті або регістрі і мати розмір 8, 16 або 32 біт. Місцезнаходження поділеного фіксоване і як і, як у команді множення, залежить від розміру операндов. Результатом команди поділу є значення частки та залишку.

Варіанти розташування та розмірів операндів операції поділу показані у таблиці 11.

Таблиця 11. Розташування операндів та результату при розподілі

Після виконання команди поділу вміст прапорів невизначений, але можливе виникнення переривання з номером 0, званого "поділ на нуль". Цей вид переривання відноситься до так званих винятків. Цей різновид переривань виникає всередині мікропроцесора через деякі аномалії під час обчислювального процесу. Переривання О, "розподіл на нуль", при виконанні команди div може виникнути з однієї з наступних причин:

1) дільник дорівнює нулю;

2) приватне не входить у відведену під нього розрядну сітку, що може статися у таких випадках:

а) при розподілі поділюваного величиною в слово на дільник величиною в байт, причому значення поділюваного у більш ніж 256 разів більше значення дільника;

б) при розподілі поділюваного величиною в подвійне слово на дільник величиною в слово, причому значення поділюваного у більш ніж 65 разів більше значення дільника;

в) при розподілі поділюваного величиною в чотиридверне слово на дільник величиною в подвійне слово, причому значення поділеного в більш ніж 4 разів більше значення дільника.

Розподіл чисел зі знаком

Для поділу чисел зі знаком призначена команда

idiv дільник

Для цієї команди справедливі всі розглянуті положення щодо команд і чисел зі знаком. Зазначимо лише особливості виникнення винятку 0, " розподіл на нуль " , у разі чисел зі знаком. Воно виникає при виконанні команди idiv з однієї з таких причин:

1) дільник дорівнює нулю;

2) приватне не входить у відведену йому розрядну сітку.

Останнє у свою чергу може статися:

1) при розподілі поділюваного величиною в слово зі знаком на дільник величиною в байт зі знаком, причому значення поділюваного у більш ніж 128 разів більше значення дільника (отже приватна не повинна знаходитися поза діапазоном від -128 до + 127);

2) при розподілі поділюваного величиною в подвійне слово зі знаком на дільник величиною в слово зі знаком, причому значення поділеного у більш ніж 32 768 разів більше значення дільника (таким чином, приватне не повинно знаходитися поза діапазоном від -32 768 до +32 768) ;

3) при розподілі поділюваного величиною в вчетверное слово зі знаком на дільник величиною в подвійне слово зі знаком, причому значення поділюваного у більш ніж 2 разів більше значення дільника (таким чином, приватне не повинно знаходитися поза діапазоном від -147 до +483).

Допоміжні команди для цілих операцій

У системі команд мікропроцесора є кілька команд, які можуть полегшити програмування алгоритмів, які виробляють арифметичні обчислення. Вони можуть виникати різні проблеми, вирішення яких розробники мікропроцесора передбачили кілька команд.

Команди перетворення типів

Що робити, якщо розміри операндів, що беруть участь у арифметичних операціях, є різними? Наприклад, припустимо, що в операції додавання один операнд є словом, а інший займає подвійне слово. Вище сказано, що в операції додавання повинні брати участь операнди одного формату. Якщо числа без знаку, то вихід знайти просто. І тут можна з урахуванням вихідного операнда сформувати новий (формату подвійного слова), старші розряди якого легко заповнити нулями. Складніша ситуація для чисел зі знаком: як динамічно, під час виконання програми, врахувати знак операнда? Для вирішення подібних проблем у системі команд мікропроцесора є звані команди перетворення типу. Ці команди розширюють байти на слова, слова - на подвійні слова і подвійні слова - на чотири слова (64-розрядні значення). Команди перетворення типу особливо корисні при перетворенні цілих зі знаком, оскільки вони автоматично заповнюють старші біти новоствореного операнда значеннями знакового біта старого об'єкта. Ця операція призводить до цілих значень того ж знака і тієї ж величини, що й вихідна, але вже у довшому форматі. Подібне перетворення називається операцією розповсюдження знака.

Існують два види команд перетворення типу.

1. Команди без операндів. Ці команди працюють із фіксованими регістрами:

1) cbw (Convert Byte to Word) - команда перетворення байта (у регістрі al) у слово (у регістрі ах) шляхом поширення значення старшого біта al на всі біти регістра ah;

2) cwd (Convert Word to Double) - команда перетворення слова (у регістрі ах) у подвійне слово (у регістрах dx: ax) шляхом поширення значення старшого біта ах на всі біти регістра dx;

3) cwde (Convert Word to Double) - команда перетворення слова (у регістрі ах) у подвійне слово (у регістрі еах) шляхом поширення значення старшого біта ах на всі біти старшої половини регістру еах;

4) cdq (Convert Double Word to Quarter Word) - команда перетворення подвійного слова (у регістрі еах) на чотири слова (у регістрах edx: eax) шляхом поширення значення старшого біта еах на всі біти регістру edx.

2. Команди movsx і movzx, які стосуються команд обробки рядків. Ці команди мають корисну властивість у контексті нашої проблеми:

1) movsx операнд_1, операнд_2 – переслати з поширенням знака. Розширює 8 або 16-розрядне значення операнд_2, яке може бути регістром або операндом у пам'яті, до 16 або 32-розрядного значення в одному з регістрів, використовуючи значення знакового біта для заповнення старших позицій операнд_1. Цю команду зручно використовуватиме підготовки операндів зі знаками до виконання арифметичних дій;

2) movzx операнд_1, операнд_2 – переслати з розширенням нулем. Розширює 8- або 16-розрядне значення операнд_2 до 16- або 32-розрядного з очищенням (заповненням) нулями старших позицій операнд_2. Цю команду зручно використовуватиме підготовки операндів без знака до виконання арифметичних дій.

Інші корисні команди

1. xadd призначення, джерело - обмін місцями та додавання.

Команда дозволяє виконати послідовно дві дії:

1) обміняти значення призначення та джерело;

2) помістити на місце операнда призначення суму: призначення = призначення + джерело.

2. neg операнд – заперечення з доповненням до двох.

Команда виконує інвертування значення операнд. Фізично команда виконує одну дію:

операнд = 0 - операнд, тобто віднімає операнд з нуля.

Команду neg операнд можна застосовувати:

1) зміни знака;

2) до виконання віднімання з константи.

Арифметичні операції над двійково-десятковими числами

У цьому розділі ми розглянемо особливості кожного з чотирьох основних арифметичних дій для упакованих та невпакованих двійково-десяткових чисел.

Справедливо може виникнути питання: а навіщо потрібні числа BCD? Відповідь може бути наступною: BCD-числа потрібні в ділових додатках, тобто там, де числа мають бути більшими та точними. Як ми вже переконалися з прикладу двійкових чисел, операції з такими числами досить проблематичні мови асемблера. До недоліків використання двійкових чисел можна віднести такі:

1) значення величин у форматі слова та подвійного слова мають обмежений діапазон. Якщо програма призначена для роботи в галузі фінансів, то обмеження суми в рублях величиною 65 (для слова) або навіть 536 (для подвійного слова) буде істотно звужувати сферу її застосування;

2) наявність помилок округлення. Уявляєте програму, яка працює десь у банку, яка не враховує величину залишку при діях з цілими двійковими числами і оперує при цьому мільярдами? Не хотілося б бути автором такої програми. Застосування чисел з плаваючою точкою не врятує – там існує та сама проблема округлення;

3) представлення великого обсягу результатів у символьному вигляді (ASCII-код). Ділові програми не просто виконують обчислення; однією з цілей їхнього використання є оперативна видача інформації користувачеві. Для цього, природно, інформація має бути подана у символьному вигляді. Переведення чисел із двійкового коду в ASCII-код потребує певних обчислювальних витрат. Число з плаваючою точкою набагато складніше перевести в символьний вигляд. А от якщо подивитися на шістнадцяткове уявлення невпакованої десяткової цифри і на відповідний символ у таблиці ASCII, то видно, що вони відрізняються на величину 30h. Таким чином, перетворення на символьний вигляд і назад виходить набагато простіше та швидше.

Напевно, ви вже переконалися у важливості оволодіння хоча б основами дій із десятковими числами. Далі розглянемо особливості виконання основних арифметичних операцій із десятковими числами. Відзначимо відразу той факт, що окремих команд складання, віднімання, множення та поділу BCD чисел немає. Зроблено це з цілком зрозумілих причин: розмірність таких чисел може бути як завгодно великою. Складати і віднімати можна двійково-десяткові числа, як у упакованому форматі, так і в невпакованому, а ось ділити і множити можна лише невпаковані BCD-числа. Чому це так, буде видно з подальшого обговорення.

Арифметичні дії над невпакованими BCD-числами

Додавання невпакованих BCD-чисел

Розглянемо два випадки складання.

Приклад

Результат додавання не більше 9

6 = 0000

+

3 = 0000

=

9 = 0000

Перенесення з молодшого зошита до старшої немає. Результат правильний.

Приклад

Результат додавання більше 9:

06 = 0000

+

07 = 0000

=

13 = 0000

Ми отримали вже не BCD-число. Результат неправильний. Правильний результат у неупакованому BCD-форматі має бути таким: 0000 0001 0000 0011 у двійковому поданні (або 13 у десятковому).

Проаналізувавши цю проблему при додаванні BCD-чисел (і подібні проблеми при виконанні інших арифметичних дій) і можливі шляхи її вирішення, розробники системи команд мікропроцесора вирішили не вводити спеціальні команди для роботи з BCD-числами, а ввести кілька команд.

Призначення цих команд - у коригуванні результату роботи звичайних арифметичних команд для випадків, коли операнди у яких є BCD-числами.

У разі віднімання прикладі 10 видно, що отриманий результат потрібно коригувати. Для корекції операції складання двох однозначних невпакованих BCD-чисел у системі команд мікропроцесора існує спеціальна команда - ааа (ASCII Adjust for Addition) - корекція результату додавання у символьному вигляді.

Ця команда не має операндів. Вона працює неявно тільки з регістром al і аналізує значення його молодшого зошита:

1) якщо це значення менше 9, то прапор cf скидається в Про здійснюється перехід до наступної команди;

2) якщо це значення більше 9, то виконуються такі дії:

а) до вмісту молодшого зошита al (але не до всього регістру!) додається 6, тим самим значення десяткового результату коригується в правильну сторону;

б) прапор cf встановлюється в 1, тим самим фіксується перенесення у старший розряд, щоб його можна було врахувати в наступних діях.

Так, у прикладі 10, припускаючи, що значення суми 0000 1101 знаходиться в al, після команди ааа в регістрі буде 1101 + 0110 = 0011, тобто двійкове 0000 0011 або десяткове 3 а прапор cf встановиться в 1 т. · Перенесення запам'ятався в мікропроцесорі. Далі програмісту потрібно буде використовувати команду додавання adc, яка врахує перенесення з попереднього розряду.

Віднімання невпакованих BCD-чисел

Ситуація тут цілком аналогічна до додавання. Розглянемо самі випадки.

Приклад

Результат віднімання не більше 9:

6 = 0000

-

3 = 0000

=

3 = 0000

Як бачимо, позики зі старшого зошита немає. Результат вірний та коригування не вимагає.

Приклад

Результат віднімання більше 9:

6 = 0000

-

7 = 0000

=

-1 = 1111 1111

Віднімання проводиться за правилами двійкової арифметики. Тому результат не є числом BCD.

Правильний результат у неупакованому BCD-форматі має бути 9 (0000 1001 у двійковій системі числення). При цьому передбачається позика зі старшого розряду, як при звичайній команді віднімання, тобто у випадку з BCD числами фактично має бути виконано віднімання 16 - 7. Таким чином, видно: як і у разі додавання, результат віднімання потрібно коригувати. І тому існує спеціальна команда - aas (ASCII Adjust for Substraction) - корекція результату віднімання подання у символьному вигляді.

Команда aas також не має операндів і працює з регістром al, аналізуючи його молодший зошит так:

1) якщо її значення менше 9, прапор cf скидається в 0 і управління передається наступній команді;

2) якщо значення зошита al більш 9, то команда aas виконує такі дії:

а) з вмісту молодшого зошита регістру al (зауважте - не з вмісту всього регістра) віднімає 6;

б) обнуляє старший зошит регістру al;

в) встановлює прапор cf в 1, тим самим фіксуючи уявну позику зі старшого розряду.

Зрозуміло, що команда aas застосовується разом із основними командами віднімання sub і sbb. При цьому команду sub є сенс використовувати лише один раз, при відніманні наймолодших цифр операндів, далі повинна застосовуватися команда sbb, яка враховуватиме можливу позику зі старшого розряду.

Розмноження невпакованих BCD-чисел

На прикладі додавання та віднімання невпакованих чисел стало зрозуміло, що стандартних алгоритмів для виконання цих дій над BCD-числами немає і програміст повинен сам, виходячи з вимог до своєї програми, реалізувати ці операції.

Реалізація двох операцій, що залишилися - множення і поділу - ще більш складна. У системі команд мікропроцесора присутні лише засоби для множення та розподілу однорозрядних невпакованих BCD-чисел.

Щоб множити числа довільної розмірності, потрібно реалізувати процес множення самостійно, взявши за основу деякий алгоритм множення, наприклад " стовпчик " .

Для того, щоб перемножити два однорозрядні BCD-числа, необхідно:

1) помістити один із співмножників у регістр AL (як того вимагає команда mul);

2) помістити другий операнд у регістр чи пам'ять, відвівши байт;

3) перемножити співмножники командою mul (результат, як і належить, буде в ах);

4) результат, звичайно, вийде у двійковому коді, тому його потрібно скоригувати.

Для корекції результату після множення застосовується спеціальна команда – aam (ASCII Adjust for Multiplication) – корекція результату множення для подання у символьному вигляді.

Вона не має операндів і працює з регістром АХ наступним чином:

1) ділить al на 10;

2) результат поділу записується так: приватне al, залишок в ah. В результаті після виконання команди aam у регістрах AL та ah знаходяться правильні двійково-десяткові цифри добутку двох цифр.

Перед закінченням обговорення команди aam необхідно відзначити ще один варіант застосування. Цю команду можна застосовувати для перетворення двійкового числа в регістрі AL в неупаковане BCD-число, яке буде розміщено в регістрі ах: старша цифра результату ah, молодша - al. Зрозуміло, що двійкове число має бути в діапазоні 0...99.

Розподіл неупакованих BCD чисел

Процес виконання операції розподілу двох невпакованих BCD-чисел дещо відрізняється від інших, розглянутих раніше операцій із ними. Тут також потрібні дії з корекції, але вони повинні здійснюватися до основної операції, що безпосередньо виконує поділ одного BCD-числа на інше BCD-число. Попередньо в регістрах потрібно отримати дві невпаковані BCD-цифри ділимого. Це робить програміст зручним для нього способом. Далі потрібно видати команду aad – aad (ASCII Adjust for Division) – корекція поділу для подання у символьному вигляді.

Команда не має операндів і перетворює двозначне незапаковане BCD-число в регістрі ах у двійкове число. Це двійкове число згодом відіграватиме роль діленого в операції поділу. Крім перетворення, команда aad поміщає отримане двійкове число регістр AL. Ділене, природно, буде двійковим числом з діапазону 0...99.

Алгоритм, за яким команда aad здійснює це перетворення, полягає в наступному:

1) помножити старшу цифру вихідного BCD-числа в ах (вміст АН) на 10;

2) виконати додавання АН + AL, результат якого (двійкове число) занести до AL;

3) обнулити вміст АН.

Далі програмісту потрібно видати звичайну команду поділу div для виконання поділу вмісту ах на одну BCD-цифру, що знаходиться в байтовому регістрі або байтовій комірці пам'яті.

Аналогічно ааш, команді aad можна знайти й інше застосування - використовувати її для перекладу невпакованих BCD-чисел з діапазону 0...99 в їхній двійковий еквівалент.

Для розподілу чисел більшої розрядності, як і у разі множення, потрібно реалізовувати свій алгоритм, наприклад " в стовпчик " , чи знайти оптимальний шлях.

Арифметичні дії над упакованими BCD-числами

Як зазначалося вище, упаковані BCD-числа можна лише складати і віднімати. Для виконання інших дій над ними їх потрібно додатково перетворювати або на невпакований формат, або в двійкове уявлення. Через те, що упаковані BCD-числа становлять невеликий інтерес, ми їх розглянемо коротко.

Додавання упакованих BCD-чисел

Спочатку розберемося з суттю проблеми і спробуємо скласти два двозначні упаковані BCD-числа. Приклад Додавання упакованих BCD чисел:

67 = 01100111

+

75 = 01110101

=

142 = 1101 1100 = 220

Як бачимо, у двійковому вигляді результат дорівнює 1101 1100 (або 220 у десятковому поданні), що неправильно. Це відбувається з тієї причини, що мікропроцесор не підозрює про існування BCD чисел і складає їх за правилами складання двійкових чисел. Насправді, результат у двійково-десятковому вигляді має дорівнювати 0001 0100 0010 (або 142 у десятковому поданні).

Видно, що, як і для невпакованих BCD-чисел, для упакованих BCD-чисел існує потреба якось коригувати результати арифметичних операцій.

Мікропроцесор надає при цьому команду daa - daa (Decimal Adjust for Addition) - корекція результату додавання у десятковому вигляді.

Команда daa перетворює вміст регістру al у дві упаковані десяткові цифри за алгоритмом, наведеним в описі команди daa Одиниця (якщо результат складання більше 99), що вийшла в результаті складання, запам'ятовується у прапорі cf, тим самим враховується перенесення у старший розряд.

Віднімання упакованих BCD чисел

Аналогічно до складу, мікропроцесор розглядає упаковані BCD-числа як двійкові і, відповідно, виконує віднімання BCD-чисел як двійкові.

Приклад

Віднімання упакованих BCD чисел.

Виконаємо віднімання 67-75. Так як мікропроцесор виконує віднімання способом додавання, то і ми підемо цьому:

67 = 01100111

+

-75 = 10110101

=

-8 = 0001 1100 = 28

Як бачимо, результат дорівнює 28 у десятковій системі числення, що є абсурдом. У двійково-десятковому коді результат повинен дорівнювати 0000 1000 (або 8 у десятковій системі числення).

При програмуванні віднімання упакованих BCD-чисел програміст, як і віднімання невпакованих BCD-чисел, повинен сам здійснювати контроль за знаком. Це робиться за допомогою прапора CF, який фіксує позику зі старших розрядів.

Саме віднімання BCD-чисел здійснюється простою командою віднімання sub або sbb. Корекція результату здійснюється командою das – das (Decimal Adjust for Substraction) – корекція результату віднімання для подання у десятковому вигляді.

Команда das перетворює вміст регістру AL на дві упаковані десяткові цифри за алгоритмом, наведеним в описі команди das.

ЛЕКЦІЯ № 19. Команди передачі управління

1. Логічні команди

Поряд із засобами арифметичних обчислень, система команд мікропроцесора має також засоби логічного перетворення даних. Під логічними розуміються такі перетворення даних, основу яких лежать правила формальної логіки.

Формальна логіка працює на рівні тверджень істинно і хибно. Для мікропроцесора це зазвичай означає 1 і 0 відповідно. Для комп'ютера мова нулів та одиниць є рідною, але мінімальною одиницею даних, з якою працюють машинні команди, є байт. Однак на системному рівні часто необхідно мати можливість працювати на гранично низькому рівні – на рівні біт.

Рис. 29. Засоби логічного оброблення даних

До засобів логічного перетворення даних відносяться логічні команди та логічні операції. Операнд команди асемблера у випадку може бути вираз, яке, своєю чергою, є комбінацій операторів і операндов. Серед цих операторів можуть і оператори, реалізують логічні операції над об'єктами висловлювання.

Перед докладним розглядом цих коштів розглянемо, що є самі логічні дані і які операції з них проводяться.

Логічні дані

Теоретичною базою для логічного оброблення даних є формальна логіка. Існує кілька систем логіки. Одна з найвідоміших – це літочислення висловлювань. Висловлювання - це будь-яке твердження, про яке можна сказати, що воно або істинно, або хибно.

Обчислення висловлювань є сукупність правил, використовуваних визначення істинності чи хибності деякої комбінації висловлювань.

Обчислення висловлювань дуже гармонійно поєднується із принципами роботи комп'ютера та основними методами його програмування. Усі апаратні компоненти комп'ютера побудовано логічних мікросхемах. Система представлення інформації в комп'ютері на нижньому рівні полягає в понятті біта. Біт, маючи всього два стани (0 (хибно) і 1 (істинно)), природним чином вписується в обчислення висловлювань.

Відповідно до теорії, над висловлюваннями (над бітами) можуть виконуватися такі логічні операції.

1. Заперечення (логічне НЕ) - логічна операція над одним операндом, результатом якої є величина, обернена до значення вихідного операнда.

Ця операція однозначно характеризується наступною таблицею істинності (табл. 12).

Таблиця 12. Таблиця істинності для логічного заперечення

2. Логічне складання (логічне включає АБО) - логічна операція над двома операндами, результатом якої є "істина" (1), якщо один або обидва операнда мають значення "істина" (1), і "брехня" (0), якщо обидва операнда мають значення "брехня" (0).

Ця операція описується за допомогою наступної таблиці істинності (табл. 13).

Таблиця 13. Таблиця істинності для логічного, що включає АБО

3. Логічне множення (логічне І) - логічна операція над двома операндами, результатом якої є "істина" (1) тільки в тому випадку, якщо обидва операнди мають значення "істина" (1). У решті випадків значення операції "брехня" (0).

Ця операція описується за допомогою наступної таблиці істинності (табл. 14).

Таблиця 14. Таблиця істинності для логічного І

4. Логічне виключне додавання (логічне виключне АБО) - логічна операція над двома операндами, результатом якої є "істина" (1), якщо тільки один з двох операндів має значення "істина" (1), і брехня (0), якщо обидва операнда мають значення "брехня" (0) або "істина" (1). Ця операція описується за допомогою наступної таблиці істинності (табл. 15).

Таблиця 15. Таблиця істинності для логічного виключає АБО

Система команд мікропроцесора містить п'ять команд, які підтримують ці операції. Ці команди виконують логічні операції над бітами операндів. Розмірність операндів, звісно, ​​має бути однакова. Наприклад, якщо розмірність операнда дорівнює слову (16 біт), то логічна операція виконується спочатку над нульовими бітами операнда, і її результат записується на місце біта 0 результату. Далі команда послідовно повторює ці дії над усіма бітами з першого до п'ятнадцятого.

Логічні команди

У системі команд мікропроцесора є наступний набір команд, які підтримують роботу з логічними даними:

1) and операнд_1, операнд_2 – операція логічного множення. Команда виконує порозрядно логічну операцію І (кон'юнкцію) над бітами операндів операнд_1 та операнд_2. Результат записується на місце операнд_1;

2) операнд_1, операнд_2 - операція логічного складання. Команда виконує порозрядно логічну операцію АБО (диз'юнкцію) над бітами операндів операнд_1 та операнд_2. Результат записується на місце операнд_1;

3) хог операнд_1, операнд_2 - операція логічного виключає складання. Команда виконує порозрядно логічну операцію виключає АБО над бітами операндів операнд_1 і операнд_2. Результат записується на місце операнді;

4) test операнд_1, операнд_2 - операція "перевірити" (спосібом логічного множення). Команда виконує порозрядно логічну операцію І над бітами операндів операнд_1 та операнд_2. Стан операнда залишається колишнім, змінюються тільки прапори zf, sf, і pf, що дає можливість аналізувати стан окремих бітів операнда без зміни їх стану;

5) not операнд – операція логічного заперечення. Команда виконує порозрядне інвертування (заміну значення зворотне) кожного біта операнда. Результат записується на місце операнда.

Для представлення ролі логічних команд у системі команд мікропроцесора дуже важливо зрозуміти сфери їх застосування та типові прийоми їх використання при програмуванні.

За допомогою логічних команд можливе виділення окремих бітів в операнді з метою встановлення, скидання, інвертування або просто перевірки на певне значення.

Для організації подібної роботи з бітами операнд_2 грає роль маски. За допомогою встановлених в 1 биті цієї маски визначаються потрібні для конкретної операції біти операнд_1. Покажемо, які логічні команди можуть застосовуватися для цієї мети:

1) для установки певних розрядів (біт) в 1 застосовується команда операнд_1, операнд_2.

У цій команді операнд_2, що виконує роль маски, повинен містити одиничні біти на місці розрядів, які повинні бути встановлені в 1 в операнд_1;

2) для скидання певних розрядів (біт) 0 застосовується команда and операнд_1, операнд_2.

У цій команді операнд_2, що виконує роль маски, повинен містити нульові біти на місці розрядів, які повинні бути встановлені в 0 в операнд_1;

3) команда хог операнд_1, операнд_2 застосовується:

а) для з'ясування того, які біти в операнд_1 та операнді різняться;

б) для інвертування стану заданих біт операнд_1.

Цікаві біти маски (операнд_2) при виконанні команди хог повинні бути одиничними, інші - нульовими;

Для перевірки стану заданих біт застосовується команда test операнд_1, операнд_2 (перевірити операнд_1).

Перевірені біти операнд_1 масці (операнд_2) повинні мати одиничне значення. Алгоритм роботи команди test подібний до алгоритму команди and, але він не змінює значення операнд_1. Результатом команди є встановлення значення прапора нуля zf:

1) якщо zf = 0, то в результаті логічного множення вийшов нульовий результат, тобто один одиничний біт маски, який не збігся з відповідним одиничним бітом операнді;

2) якщо zf = 1, то в результаті логічного множення вийшов ненульовий результат, тобто хоча один одиничний біт маски збігся з відповідним одиничним бітом операнд_1.

Для реакції на результат команди test доцільно використовувати команду переходу jnz мітка (Jump if Not Zero) – перехід, якщо прапор нуля zf ненульовий, або команду зі зворотною дією – jz мітка (Jump if Zero) – перехід, якщо прапор нуля zf = 0.

Наступні дві команди дозволяють здійснити пошук першого встановленого в 1 біт операнда. Пошук можна зробити як з початку, так і від кінця операнда:

1) bsf операнд_1, операнд_2 (Bit Scaning Forward) – сканування бітів вперед. Команда переглядає (сканує) біти операнд_2 від молодшого до старшого (від біта 0 до старшого біта) у пошуках першого біта, встановленого в 1. Якщо такий виявляється, в операнд_1 заноситься номер цього біта у вигляді цілого числа. Якщо всі біти операнд_2 дорівнюють 0, то прапор нуля zf встановлюється в 1, інакше прапор zf скидається в 0;

2) bsr операнд_1, операнд_2 (Bit Scaning Reset) – сканування бітів у зворотному порядку. Команда переглядає (сканує) біти операнд_2 від старшого до молодшого (від старшого біта до біта 0) у пошуках першого біта, встановленого в 1. Якщо такий виявляється, операнд_1 заноситься номер цього біта у вигляді цілочисельного значення. При цьому важливо, що позиція першого одиничного біта зліва відраховується однаково щодо біта 0. Якщо всі біти операнд_2 дорівнюють 0, то прапор нуля zf встановлюється в 1, інакше прапор zf скидається в 0.

В останніх моделях мікропроцесорів Intel у групі логічних команд з'явилося ще кілька команд, які дозволяють здійснити доступ до одного конкретного біта операнда. Операнд може бути як у пам'яті, і у регістрі загального призначення. Положення біта визначається зсувом біта щодо молодшого біта операнда. Значення зміщення може задаватися як безпосереднього значення, і утримуватися у регістрі загального призначення. Як значення зміщення можна використовувати результати роботи команд bsr і bsf. Усі команди надають значення обраного біта прапору РЄ

1) bt операнд, смещение_бита (Bit Test) – перевірка біта. Команда переносить значення біта у прапор cf;

2) bts операнд, смещение_бита (Bit Test and Set) – перевірка та встановлення біта. Команда переносить значення біта у прапор CF і потім встановлює біт, що перевіряється, в 1;

3) btr операвд, смещение_бита (Bit Test and Reset) - перевірка та скидання біта. Команда переносить значення біта у прапор CF і потім встановлює цей біт 0;

4) btc операнд, смещение_бита (Bit Test and Convert) – перевірка та інвертування біта. Команда переносить значення біта у прапор cf і потім інвертує значення цього біта.

Команди зсуву

Команди цієї групи також забезпечують маніпуляції над окремими бітами операндів, але в інший спосіб, ніж логічні команди, розглянуті вище.

Усі команди зсуву переміщують біти в полі операнда ліворуч або праворуч залежно від коду операції. Усі команди зсуву мають однакову структуру - коп операнд, лічильник_зсувів.

Кількість розрядів, що зсуваються - лічильник_зрушень - розташовується на місці другого операнда і може задаватися двома способами:

1) статично, що передбачає завдання фіксованого значення за допомогою безпосереднього операнда;

2) динамічно, що означає занесення значення лічильника зсувів регістр cl перед виконанням команди зсуву.

З розмірності регістру cl відомо, що значення лічильника зрушень може лежати в діапазоні від 0 до 255. Але насправді це зовсім так. З метою оптимізації мікропроцесор приймає лише значення п'яти молодших бітів лічильника, т. е. значення лежить у діапазоні від 0 до 31.

Усі команди зсуву встановлюють прапор перенесення cf.

У міру зсуву бітів за межі операнда вони спочатку потрапляють на прапор перенесення, встановлюючи його рівним значенню чергового біта, що опинився за межами операнда. Куди цей біт потрапить далі, залежить від типу команди зсуву та алгоритму програми.

За принципом дії команди зсуву можна поділити на два типи:

1) команди лінійного зсуву;

2) команди циклічного зсуву.

Команди лінійного зсуву

До команд цього типу відносяться команди, які здійснюють зсув за наступним алгоритмом:

1) черговий "висуваний" біт встановлює прапор CF;

2) біт, що вводиться в операнд з іншого кінця, має значення 0;

3) при зрушенні чергового біта він переходить у прапор CF, при цьому значення попереднього зрушеного біта втрачається! Команди лінійного зсуву діляться на два підтипи:

1) команди логічного лінійного зсуву;

2) команди арифметичного лінійного зсуву.

До команд логічного лінійного зсуву належать такі:

1) shl операнд, лічильник_зсувів (Shift Logical Left) – логічний зсув вліво. Вміст операнда зсувається вліво на кількість бітів, що визначається значенням сдвигов. Праворуч (у позицію молодшого біта) вписуються нулі;

2) shr операнд, лічильник_зсувів (Shift Logical Right) – логічний зсув вправо. Вміст операнда зсувається праворуч на кількість бітів, що визначається значенням лічильник_зсувів. Ліворуч (у позицію старшого, знакового біта) вписуються нулі.

На малюнку 30 показано принцип роботи цих команд.

Рис. 30. Схема роботи команд лінійного логічного зсуву

Команди арифметичного лінійного зсуву від команд логічного зсуву тим, що вони особливим чином працюють із знаковим розрядом операнда.

1) sal операнд, лічильник_зсувів (Shift Arithmetic Left) – арифметичний зсув вліво. Вміст операнда зсувається вліво на кількість бітів, що визначається значенням сдвигов. Праворуч (у позицію молодшого біта) вписуються нулі. Команда sal не зберігає знака, але встановлює прапор з/у разі зміни знака черговим бітом, що висувається. В іншому команда sal повністю аналогічна команді shl;

2) sar операнд, лічильник_зсувів (Shift Arithmetic Right) – арифметичний зсув вправо. Вміст операнда зсувається праворуч на кількість бітів, що визначається значенням лічильник_зсувів. Зліва в операнд вписуються нулі. Команда sar зберігає знак, відновлюючи його після зсуву кожного біта.

На малюнку 31 показано принцип роботи команд лінійного арифметичного зсуву.

Рис. 31. Схема роботи команд лінійного арифметичного зсуву

Команди циклічного зсуву

До команд циклічного зсуву відносяться команди, що зберігають значення біт, що зрушуються. Є два типи команд циклічного зсуву:

1) команди простого циклічного зсуву;

2) команди циклічного зсуву через прапор перенесення cf.

До команд простого циклічного зсуву належать:

1) rol операнд, лічильник_зсувів (Rotate Left) – циклічний зсув вліво. Вміст операнда зсувається вліво на кількість біт, що визначається операндом лічильник_зсувів. Зсувні ліворуч біти записуються в той же операнд праворуч;

2) гог операнд, лічильник_зсувів (Rotate Right) – циклічний зсув вправо. Вміст операнда зсувається праворуч на кількість біт, що визначається операндом лічильник_зрушень. Зсувні вправо біти записуються в той же операнд зліва.

Рис. 32. Схема роботи команд простого циклічного зсуву

Як видно з малюнка 32, команди простого циклічного зсуву в процесі своєї роботи здійснюють одну корисну дію, а саме: біт, що циклічно зрушується не тільки всувається в операнд з іншого кінця, але і одночасно його значення стає значенням прапора РЄ

Команди циклічного зсуву через прапор переносу CF відрізняються від команд простого циклічного зсуву тим, що біт, що зрушується, не відразу потрапляє в операнд з іншого його кінця, а записується спочатку у прапор переносу РЄ Лише наступне виконання даної команди зсуву (за умови, що вона виконується в циклі) призводить до приміщенню висунутого раніше біта з іншого кінця операнда (рис. 33).

До команд циклічного зсуву через прапор переносу співвідносяться такі:

1) rcl операнд, лічильник_зсувів (Rotate through Carry Left) – циклічний зсув вліво через перенесення.

Вміст операнда зсувається вліво на кількість біт, що визначається операндом лічильник_зсувів. Збиті біти по черзі стають значенням прапора переносу cf.

2) гсг операнд, лічильник_зсувів (Rotate through Carry Right) – циклічний зсув вправо через перенесення.

Вміст операнда зсувається праворуч на кількість біт, що визначається операндом лічильник_зсувів. Збиті біти по черзі стають значенням прапора переносу СF.

Рис. 33. Команди циклічного зсуву через прапор переносу CF

З малюнка 33 видно, що при зрушенні через прапор переносу з'являється проміжний елемент, за допомогою якого, зокрема, можна проводити заміну бітів, що циклічно зсуваються, зокрема, неузгодження бітових послідовностей.

Під неузгодженістю бітової послідовності тут і далі мається на увазі дія, яка дозволяє деяким чином локалізувати та витягти потрібні ділянки цієї послідовності та записати їх в інше місце.

Додаткові команди зсуву

Система команд останніх моделей мікропроцесорів Intel, починаючи з i80386, містить додаткові зсувні команди, що розширюють можливості, розглянуті нами раніше. Це команди зрушень подвійної точності:

1) shld операнд_1, операнд_2, лічильник_зрушень - зрушення вліво подвійної точності. Команда shld здійснює заміну шляхом зсуву бітів операнда операнд_1 вліво, заповнюючи його біти праворуч значеннями бітів, що витісняються з операнд_2 згідно зі схемою на рис. 34. Кількість біт, що зсуваються, визначається значенням лічильник_зрушень, яке може лежати в діапазоні 0... 31. Це значення може задаватися безпосереднім операндом або утримуватися в регістрі cl. Значення операнд_2 не змінюється.

Рис. 34. Схема роботи команди shld

2) shrd операнд_1, операнд_2, лічильник_зсувів - зсув праворуч подвійної точності. Команда проводить заміну шляхом зсуву бітів операнда операнд_1 вправо, заповнюючи його біти зліва значеннями бітів, що витісняються з операнд_2 згідно з схемою на малюнку 35. Кількість зсувних біт визначається значенням лічильник_зсувів, яке може лежати в діапазоні 0... 31. Це значення може задавати або утримуватися в регістрі cl. Значення операнд_2 не змінюється.

Рис. 35. Схема роботи команди shrd

Як ми зазначили, команди shld та shrd здійснюють зрушення до 32 розрядів, але за рахунок особливостей завдання операндів та алгоритму роботи ці команди можна використовувати для роботи з полями завдовжки до 64 біт.

2. Команди передачі управління

Ми познайомились із деякими командами, з яких формуються лінійні ділянки програми. Кожна з них у загальному випадку виконує деякі дії щодо перетворення або пересилання даних, після чого мікропроцесор передає керування наступній команді. Але дуже мало програм працює таким послідовним чином. Зазвичай у програмі є точки, в яких потрібно ухвалити рішення про те, яка команда виконуватиметься наступною. Це рішення може бути:

1) безумовним - у цій точці необхідно передати управління не тій команді, яка йде наступною, а іншою, яка знаходиться на деякому віддаленні від поточної команди;

2) умовним – рішення про те, яка команда виконуватиметься наступною, приймається на основі аналізу деяких умов або даних.

Програма є послідовністю команд і даних, що займають певний простір оперативної пам'яті. Цей простір пам'яті може бути безперервним, або складатися з кількох фрагментів.

Те, яка команда програми має виконуватися наступною, мікропроцесор дізнається за вмістом кількох регістрів cs:(e)ip:

1) cs - сегментний регістр коду, в якому знаходиться фізична (базова) адреса поточного сегмента коду;

2) eip/ip - регістр покажчика команди, в якому знаходиться значення, що представляє собою зміщення в пам'яті наступної команди, що підлягає виконанню щодо початку поточного сегмента коду.

Який регістр буде використовуватися, залежить від встановленого режиму адресації use16 або use32. Якщо вказано use 16, використовується ip, якщо use32, то використовується eip.

Таким чином, команди передачі управління змінюють вміст регістрів cs і eip/ip, в результаті чого процесор вибирає для виконання не наступну по порядку команду програми, а команду в деякій іншій ділянці програми. Конвеєр усередині мікропроцесора при цьому скидається.

За принципом дії команди мікропроцесора, що забезпечують організацію переходів у програмі, можна поділити на 3 групи:

1. Команди безумовної передачі управління:

1) команда безумовного переходу;

2) команда виклику процедури та повернення з процедури;

3) команда виклику програмних переривань та повернення з програмних переривань.

2. Команди умовної передачі управління:

1) команди переходу за результатом команди порівняння стор;

2) команди переходу за станом певного прапора;

3) команди переходу за вмістом регістру есх/сх.

3. Команди управління циклом:

1) команда організації циклу з лічильником есх/сх;

2) команда організації циклу з лічильником есх/сх з можливістю дострокового виходу із циклу за додатковою умовою.

Безумовні переходи

Попереднє обговорення виявило деякі деталі механізму переходу. Команди переходу модифікують регістр покажчика команди eip/ip та, можливо, сегментний регістр коду cs. Що саме має бути модифіковано, залежить:

1) від типу операнда у команді безумовного переходу (ближній чи далекий);

2) від вказівки перед адресою переходу (у команді переходу) модифікатора; при цьому сама адреса переходу може знаходитися або безпосередньо в команді (прямий перехід), або в регістрі або комірці пам'яті (непрямий перехід).

Модифікатор може приймати такі значення:

1) near ptr – прямий перехід на мітку всередині поточного сегмента коду. Модифікується лише регістр eip/ip (залежно від заданого типу сегмента коду use16 або use32) на основі вказаної в команді адреси (мітки) або виразу, що використовує символ отримання значення - $;

2) far ptr – прямий перехід на мітку в іншому сегменті коду. Адреса переходу задається у вигляді безпосереднього операнда або адреси (мітки) і складається з 16-бітного селектора та 16/32-бітного зміщення, які завантажуються, відповідно, в регістри cs та ip/eip;

3) word ptr - опосередкований перехід на мітку всередині поточного сегмента коду. Модифікується (значенням зміщення з пам'яті за вказаною в команді адресою або з регістру) тільки eip/ip. Розмір усунення 16 або 32 біт;

4) dword ptr – непрямий перехід на мітку в іншому сегменті коду. Модифікуються (значенням із пам'яті - і тільки з пам'яті, з регістру не можна) обидва регістри - cs та eip/ip. Перше слово/подвійне слово цієї адреси представляє зміщення та завантажується в ip/eip; друге/третє слово завантажується в cs. Команда безумовного переходу jmp

Синтаксис команди безумовного переходу – jmp [модифікатор] адреса_переходу – безумовний перехід без збереження інформації про точку повернення.

Адрес_перехода є адресою як мітки чи адресу області пам'яті, де знаходиться покажчик переходу.

Загалом у системі команд мікропроцесора є кілька кодів машинних команд безумовного переходу jmp.

Їх відмінності визначаються дальністю переходу та способом завдання цільової адреси. Дальність переходу визначається місцем розташування операнда адрес_переходу. Ця адреса може знаходитися в поточному сегменті коду або в іншому сегменті. У першому випадку перехід називається внутрішньосегментним, або близьким, у другому - міжсегментним, або далеким. Внутрішньосегментний перехід передбачає, що змінюється лише вміст регістру eip/ip.

Можна виділити три варіанти внутрішньосегментного використання команди jmp:

1) прямий короткий;

2) прямий;

3) непрямий.

процедури

У мові асемблера є кілька засобів, які вирішують проблему дублювання ділянок програмного коду. До них відносяться:

1) механізм процедур;

2) макроассемблерів;

3) механізм переривань.

Процедура, яка часто називається також підпрограмою, - це основна функціональна одиниця декомпозиції (поділу на кілька частин) деякого завдання. Процедура являє собою групу команд для вирішення конкретної підзадачі і має засоби отримання управління з точки виклику завдання вищого рівня і повернення управління в цю точку.

У найпростішому випадку програма може складатися з однієї процедури. Іншими словами, процедуру можна визначити як правильно оформлену сукупність команд, яка, будучи одноразово описана, при необхідності може бути викликана в будь-якому місці програми.

Для опису послідовності команд як процедури в мові асемблера використовуються дві директиви: PROC і ENDP.

Синтаксис опису процедури такий (рис. 36).

Рис. 36. Синтаксис опису процедури у програмі

З малюнка 36 видно, що у заголовку процедури (директиві PROC) обов'язковим є завдання імені процедури. Серед великої кількості операнда директиви PROC слід особливо виділити [відстань]. Цей атрибут може набувати значення near або far і характеризує можливість звернення до процедури з іншого сегмента коду. За умовчанням атрибут [відстань] набуває значення near.

Процедура може розміщуватись у будь-якому місці програми, але так, щоб на неї випадковим чином не потрапило керування. Якщо процедуру просто вставити в загальний потік команд, то мікропроцесор сприйматиме команди процедури як частину цього потоку і, відповідно, здійснюватиме виконання команд процедури.

Умовні переходи

Мікропроцесор має 18 команд умовного переходу. Ці команди дозволяють перевірити:

1) відношення між операндами зі знаком ("більше - менше");

2) відношення між операндами без знака ("вище - нижче");

3) стан арифметичних прапорів ZF, SF, CF, OF, PF (але не AF).

Команди умовного переходу мають однаковий синтаксис:

jcc тега_переходу

Як бачимо, мнемокод всіх команд починається з " j " - від слова jump (стрибок), її - визначає конкретне умова, аналізоване командою.

Що стосується операнда метка_перехода, то ця мітка може перебувати лише в межах поточного сегмента коду, міжсегментна передача управління в умовних переходах не допускається. У зв'язку з цим відпадає питання про модифікатора, який був у синтаксисі команд безумовного переходу. У ранніх моделях мікропроцесора (i8086, i80186 та i80286) команди умовного переходу могли здійснювати лише короткі переходи - на відстань від -128 до +127 байт від команди, що йде за командою умовного переходу. Починаючи з моделі мікропроцесора 80386 це обмеження знято, але, як бачите, тільки в межах поточного сегмента коду.

Для того щоб прийняти рішення про те, куди буде передано управління командою умовного переходу, попередньо має бути сформована умова, на підставі якої прийматиметься рішення про передачу управління.

Джерелами такої умови можуть бути:

1) будь-яка команда, що змінює стан арифметичних прапорів;

2) команда порівняння стор, що порівнює значення двох операндів;

3) стан регістру есх/сх.

Команда порівняння cmp

Команда порівняння стор має цікавий принцип роботи. Він абсолютно такий самий, як і у команди віднімання - sub операнде, операнд_2.

Команда стор так само, як і команда sub, виконує віднімання операндів і встановлює прапори. Єдине, чого вона не робить – це запис результату віднімання на місце першого операнда.

Синтаксис команди стр - стр операнд_1, операнд_2 (compare) - порівнює два операнди і за результатами порівняння встановлює прапори.

Прапори, що встановлюються командою сторінок, можна аналізувати спеціальними командами умовного переходу. Перш ніж ми розглянемо їх, приділимо трохи уваги мнемоніці цих команд умовного переходу (табл. 16). Розуміння позначень при формуванні назви команд умовного переходу (елемент у назві команди jcc, позначений нами її) полегшить їхнє запам'ятовування та подальше практичне використання.

Таблиця 16. Значення абревіатур у назві команди jccТаблиця 17. Перелік команд умовного переходу для команди стор операнд_1, операнд_2

Не дивуйтеся тієї обставини, що однаковим значенням прапорів відповідає кілька різних мнемокодов команд умовного переходу (вони відокремлені косою межею в табл. 17). Різниця у назві обумовлена ​​бажанням розробників мікропроцесора полегшити використання команд умовного переходу у поєднанні з певними групами команд. Тому різні назви відображають швидше різну функціональну спрямованість. Проте те, що ці команди реагують на ті самі прапори, робить їх абсолютно еквівалентними та рівноправними в програмі. Тому в таблиці 17 вони згруповані не за назвами, а за значеннями прапорів (умов), на які вони реагують.

Команди умовного переходу та прапори

Мнемонічне позначення деяких команд умовного переходу відображає назву прапора, з яким вони працюють, і має таку структуру: першим йде символ "j" (Jump, перехід), другим - або позначення прапора, або символ заперечення "n", після якого стоїть назва прапора . Така структура команди відбиває її призначення. Якщо символу "n" немає, то перевіряється стан прапора, якщо він дорівнює 1, відбувається перехід на позначку переходу. Якщо символ "n" є присутнім, то перевіряється стан прапора на рівність 0, і у разі успіху здійснюється перехід на мітку переходу.

Мемокоди команд, назви прапорів та умови переходів наведені в таблиці 18. Ці команди можна використовувати після будь-яких команд, які змінюють зазначені прапори.

Таблиця 18. Команди умовного переходу та прапори

Якщо уважно подивитися на таблиці 17 і 18, видно, що багато команд умовного переходу в них є еквівалентними, оскільки в основі тих і інших лежить аналіз однакових прапорів.

Команди умовного переходу та регістр есх/сх

Архітектура мікропроцесора передбачає специфічне використання багатьох регістрів. Наприклад, регістр EAX/AX/AL використовується як акумулятор, а регістри ВР, SP - до роботи зі стеком. Регістр ЕСХ/СХ також має певне функціональне призначення: він виконує роль лічильника у командах управління циклами і під час роботи з ланцюжками символів. Можливо, що функціонально команду умовного переходу, пов'язану з регістром есх/сх, правильніше було б віднести до цієї групи команд.

Синтаксис цієї команди умовного переходу такий:

1) jcxz мітка_переходу (Jump if ex is Zero) - перехід, якщо сх нуль;

2) jecxz мітка_переходу (Jump Equal есх Zero) - перехід, якщо есх нуль.

Ці команди дуже зручно використовувати при організації циклу та при роботі з ланцюжками символів.

Слід зазначити обмеження, властиве команді jcxz/jecxz. На відміну з інших команд умовної передачі управління команда jcxz/jecxz може адресувати лише короткі переходи - на -128 байт чи +127 байт від наступної команди.

Організація циклів

Цикл, як відомо, є важливою алгоритмічною структурою, без використання якої не обходиться, напевно, жодна програма. Організувати циклічне виконання деякої ділянки програми можна, наприклад, використовуючи команди умовної передачі чи команду безумовного переходу jmp. За такої організації циклу всі операції з його організації виконуються вручну. Але, враховуючи важливість такого алгоритмічного елемента, як цикл, розробники мікропроцесора ввели у систему команд групу із трьох команд, що полегшує програмування циклів. Ці команди також використовують регістр есх/сх як лічильник циклу.

Дамо коротку характеристику цим командам:

1) loop метка_переходу (Loop) – повторити цикл. Команда дозволяє організувати цикли, подібні до циклів for у мовах високого рівня з автоматичним зменшенням лічильника циклу. Робота команди полягає у виконанні наступних дій:

а) декременту регістру ЕСХ/СГ;

б) порівняння регістру ЕСХ/СХ з нулем: якщо (ЕСХ/СХ) = 0, то управління передається на наступну після loop команду;

2) loope/loopz тега_переходу

Команди loope та loopz - абсолютні синоніми. Робота команд полягає у виконанні наступних дій:

а) декременту регістру ЕСХ/СГ;

б) порівняння регістру ЕСХ/СХ з нулем;

в) аналіз стану прапора нуля ZF якщо (ЕСХ/СХ) = 0 або XF = 0, управління передається на наступну після loop команду.

3) loopne/loopnz тега_переходу

Команди loopne та loopnz також абсолютні синоніми. Робота команд полягає у виконанні наступних дій:

а) декременту регістру ЕСХ/СГ;

б) порівняння регістру ЕСХ/СХ з нулем;

в) аналіз стану прапора нуля ZF: якщо (ЕСХ/СХ) = 0 або ZF = 1, управління передається на наступну після loop команду.

Команди loope/loopz та loopne/loopnz за принципом своєї роботи є взаємозворотними. Вони розширюють дію команди loop тим, що додатково аналізують прапор zf, що дає можливість організувати достроковий вихід із циклу, використовуючи цей прапор як індикатор.

Недолік команд організації циклу loop, loope/loopz і loopne/loopnz у тому, що вони реалізують лише короткі переходи (від -128 до +127 байт). Для роботи з довгими циклами доведеться використовувати команди умовного переходу та команду jmp, тому постарайтеся освоїти обидва способи організації циклів.

Автор: Цвєткова А.В.

Рекомендуємо цікаві статті розділу Конспекти лекцій, шпаргалки:

Історія економіки. Конспект лекцій

Терія організації. Шпаргалка

Госпітальна терапія. Конспект лекцій

Дивіться інші статті розділу Конспекти лекцій, шпаргалки.

Читайте та пишіть корисні коментарі до цієї статті.

<< Назад

Останні новини науки та техніки, новинки електроніки:

Новий спосіб управління та маніпулювання оптичними сигналами 05.05.2024

Сучасний світ науки та технологій стрімко розвивається, і з кожним днем ​​з'являються нові методи та технології, які відкривають перед нами нові перспективи у різних галузях. Однією з таких інновацій є розробка німецькими вченими нового способу керування оптичними сигналами, що може призвести до значного прогресу фотоніки. Нещодавні дослідження дозволили німецьким ученим створити регульовану хвильову пластину всередині хвилеводу із плавленого кремнезему. Цей метод, заснований на використанні рідкокристалічного шару, дозволяє ефективно змінювати поляризацію світла через хвилевід. Цей технологічний прорив відкриває нові перспективи розробки компактних і ефективних фотонних пристроїв, здатних обробляти великі обсяги даних. Електрооптичний контроль поляризації, що надається новим методом, може стати основою створення нового класу інтегрованих фотонних пристроїв. Це відкриває широкі можливості для застосування. ...>>

Приміальна клавіатура Seneca 05.05.2024

Клавіатури – невід'ємна частина нашої повсякденної роботи за комп'ютером. Однак однією з головних проблем, з якою стикаються користувачі, є шум, особливо у випадку преміальних моделей. Але з появою нової клавіатури Seneca від Norbauer & Co може змінитися. Seneca – це не просто клавіатура, це результат п'ятирічної роботи розробників над створенням ідеального пристрою. Кожен аспект цієї клавіатури, починаючи від акустичних властивостей до механічних характеристик, був ретельно продуманий і збалансований. Однією з ключових особливостей Seneca є безшумні стабілізатори, які вирішують проблему шуму, характерну для багатьох клавіатур. Крім того, клавіатура підтримує різні варіанти ширини клавіш, що робить її зручною для будь-якого користувача. І хоча Seneca поки не доступна для покупки, її реліз запланований на кінець літа. Seneca від Norbauer & Co є втіленням нових стандартів у клавіатурному дизайні. Її ...>>

Запрацювала найвища у світі астрономічна обсерваторія 04.05.2024

Дослідження космосу та її таємниць - це завдання, яка привертає увагу астрономів з усього світу. У свіжому повітрі високих гір, далеко від міських світлових забруднень, зірки та планети розкривають свої секрети з більшою ясністю. Відкривається нова сторінка в історії астрономії із відкриттям найвищої у світі астрономічної обсерваторії – Атакамської обсерваторії Токійського університету. Атакамська обсерваторія, розташована на висоті 5640 метрів над рівнем моря, відкриває нові можливості для астрономів у вивченні космосу. Це місце стало найвищим для розміщення наземного телескопа, надаючи дослідникам унікальний інструмент вивчення інфрачервоних хвиль у Всесвіті. Хоча висотне розташування забезпечує більш чисте небо та менший вплив атмосфери на спостереження, будівництво обсерваторії на високій горі є величезними труднощами та викликами. Однак, незважаючи на складнощі, нова обсерваторія відкриває перед астрономами широкі перспективи для дослідження. ...>>

Випадкова новина з Архіву

Графеновий фільтр для води 30.01.2021

Коли аркуші двовимірних наноматеріалів, таких як графен, укладаються один на одного, між аркушами утворюються крихітні проміжки, які мають безліч потенційних застосувань. Група дослідників із Університету Брауна знайшла спосіб зорієнтувати ці проміжки таким чином, щоб застосувати їх для фільтрації води та інших рідин від забруднюючих частинок нанорозмірів.

Проміжки між аркушами графена, про які йдеться, називаються наноканалами. Насправді для фільтрації води їх складно використовувати – через розташування наноканалів. Уявіть собі записник, у якому замість аркушів паперу - аркуші графена. Вони тонші у вертикальному напрямку порівняно з довжиною та шириною у горизонтальній площині. Це означає, що канали між листами також орієнтовані горизонтально, що не ідеально підходить для фільтрації: рідина повинна пройти відносно довгий шлях, щоб дістатися одного кінця каналу до іншого. Було б краще, якби канали були перпендикулярні до орієнтації листів. В цьому випадку рідина повинна буде пройти тільки відносно тонку висоту вертикальної стоси.

Вчені з Університету Брауна знайшли спосіб зробити ці наноканали вертикальними. Їх метод полягає у накладанні листів графену на еластичну підкладку, яку розтягують. Після того, як усі листи укладені, натяг основи знімається – і вона стискається. Коли це відбувається, графен збирається в гармошку, утворюючи підйоми та западини, а канали при цьому нахиляються.

Як тільки канали стають майже вертикальними, збирання покривається епоксидною смолою, а потім верхня і нижня частини обрізаються, що відкриває канали протягом усього матеріалу. Свою мембрану вчені назвали VAGME (вертикально орієнтована графенова мембрана).

Тестування мембрани показало, що водяна пара може легко просочуватися через VAGME, а гексан - більша органічна молекула - відфільтровується. Дослідники планують продовжити розробку технології з урахуванням можливих промислових чи побутових застосувань фільтрації.

Інші цікаві новини:

▪ Готується вибух греблі

▪ Розумний годинник від Samsung

▪ Електронний імплантат для контролю мозку

▪ Прання без порошку

▪ Економна подорож на електромобілі

Стрічка новин науки та техніки, новинок електроніки

 

Цікаві матеріали Безкоштовної технічної бібліотеки:

▪ розділ сайту Інфрачервона техніка. Добірка статей

▪ стаття Ісмаїл I. Знамениті афоризми

▪ стаття На голову якої статуї шотландці постійно одягають дорожні конуси? Детальна відповідь

▪ стаття Квінслендський горіх. Легенди, вирощування, способи застосування

▪ стаття Цемент для глиняного посуду. Прості рецепти та поради

▪ стаття Зарядний пристрій для стартерних акумуляторних батарей. Енциклопедія радіоелектроніки та електротехніки

Залишіть свій коментар до цієї статті:

ім'я:


E-mail (не обов'язково):


коментар:





All languages ​​of this page

Головна сторінка | Бібліотека | Статті | Карта сайту | Відгуки про сайт

www.diagram.com.ua

www.diagram.com.ua
2000-2024