- •Лекція 12 моделі пам'яті borland c. Короткі й довгі вказівники
- •1. Сегментна організація пам'яті
- •2. Моделі розподілу пам'яті
- •3. Короткі та довгі вказівники. Модифікатори вказівників
- •4. Безпосереднє програмування відеопам'яті
- •5. Особливості звертання до динамічної пам'яті через функції Borland c
3. Короткі та довгі вказівники. Модифікатори вказівників
Молодші моделі пам'яті використовують один спільний сегмент для всіх даних програми. Вони передбачають, що сегментна частина адрес усіх даних зберігається в системному регістрі DS.
Тому для адресації змінних достатньо задавати тільки два байтові значення зміщень. Відповідні вказівники, які зберігають зміщення фізичних адрес, називають короткими (чи ближніми) або near-вказівниками.
За замовчуванням двобайтовий формат встановлено для всіх вказівників у молодших моделях пам'яті (див. табл. 1).
-
(поточна межа ДП)
Зовнішня ДП
SS:FFFF
Динамічні дані
SS:SP
Локальні дані (стек)
Сегмент стека (<64К)
SS:0000
(поточна межа стека)
DS:0000
Глобальні і статичні дані
Сегмент даних (<64К)
CS(k):0000
Код програмного файла k
<64К Сегменти
<64К програмних
< 64 К кодів
CS(1):0000
Код програмного файла 1
Рис. 2. Схема розподілу оперативної пам'яті в моделі Large
У старших моделях пам'яті для даних і коду програми виділяється декілька сегментів оперативної пам'яті та використовується зовнішня динамічна пам'ять. Тому в цих моделях адреса зберігається в чотирибайтовому форматі: два старші байти задають сегмент, а два молодші - зміщення адреси.
Вказівники старших моделей називають довгими (чи дальніми) або far-вказівниками. Коли відбувається звертання до об'єкта через far-вказівник, сегмент адреси записується у відповідний сегментний регістр для формування фізичної адреси цього об'єкта.
Довгі вказівники дають змогу звернутись до кожного байта оперативної пам'яті в межах перших 1 Мбайт.
Формат вказівника, встановлений автоматично за заданою моделлю пам'яті, можна змінити, використовуючи модифікатори near, far або huge (всі три є зарезервованими словами Borland C). Модифікатор формату записують після базового типу вказівника перед символом *, наприклад:
char far* psymb;
near-, far- та huge-вказівники.
Вказівники з модифікатором near є 16-розрядними беззнаковими цілими числами, що зберігають тільки зміщення адреси, їх доцільно застосовувати в програмах, сумарний обсяг даних яких не перевищує обсягу сегмента (64 Кбайт).
Застосування коротких вказівників у багатосегментних програмах вимагає перевизначення сегментного регістра DS або використання спеціальних форм near-вказівників, про які мова йтиме далі.
Вказівники з модифікатором far 32-розрядні, вони зберігають повну адресу об'єкта, тобто сегмент і зміщення. Такі вказівники застосовують у молодших моделях пам'яті, коли потрібно звернутись до об'єктів, розташованих за межами сегмента коду програми чи сегмента даних. Зокрема, far-вказівники використовують для роботи з даними у зовнішній динамічній пам'яті, для безпосереднього звертання до відеопам'яті, для доступу до адресного простору BIOS тощо.
Наведемо приклад функції, яка дає змогу записати заданий символ в останню позицію екрана (правий нижній кут). Нагадаємо, що в разі заповнення цієї позиції будь-якою стандартною функцією виведення даних, відбувається автоматичний скролінг екранного зображення - зсув усіх рядків на один угору. Тому функція заповнення останнього знакомісця має важливе практичне значення.
/* Функція виведення символа в позиції екрана (80,25) */
void PutLastScrSmb (char symb, char attr)
{
/* symb - код символа; attr - атрибути: колір символа і колір фону */
char far* VMadr = (char far *)0xb8000000; /* початок відеопам'яті */
VMadr += (24*80+79)*2; /* адреса останнього символа екрана */
*VMadr = symb; /* відображення символа */
if (attr) /* якщо треба змінити кольори */
*(VMadr+l) =attr; }
У разі роботи з довгими вказівниками часто виникає потреба поділу значення адреси на складові частини: сегмент і зміщення, чи, навпаки, необхідність формування довгого вказівника зі складових частин. Для виконання цих операцій призначені спеціальні макроси, оголошені в заголовному файлі <dos .h>:
unsigned FP_SEG (void far* ptr);
unsigned FP_OFF (void far* ptr);
void far* MK_FP (unsigned seg, unsigned off);
Два перших макроси FP_SEG () і FP_OFF () виділяють з адреси, занесеної у far-вказівник ptr, відповідно сегментну частину та зміщення. Макрос MK_FP () формує значення довгого вказівника зі заданого сегмента seg і зміщення off. За допомогою цього макроса адресу останнього символу екранного зображення, яка використовувалася у функції PutLastScrSmb (), можна сформувати, наприклад, так:
char far* VMadr= (char far*)MK_FP(Oxb800, 25*80*2-2);
Якщо тепер оголосити:
unsigned of s = FP_OFF( VMadr); /* виділення зміщення */
то змінна ofs отримає значення 3998, що відповідає зміщенню в байтах (відносно початку відеопам'яті) знакомісця екрана з координатами (80, 25).
Іншим способом формування довгих адрес та виділення їх складових частин є використання структур і об'єднань. Такий підхід дає змогу одночасно виконувати перетворення базового типу вказівника, тобто змінювати форму доступу до даних.
Розглянемо два наступних оголошення:
struct mem_addr {
unsigned ofs, segm; /* зміщення та сегмент фізичної адреси */
};
union addr_convert {
struct mem_addr adr;
char far *pb; /* вказівники */
unsigned far *pw; /* на дані */
unsigned long far *pl; } /* різних типів */
uptc;
}
Змінна uptc має тип об'єднання union addr_convert, до складу якого входять структура adr і три вказівники з різними базовими типами: pb, pw і рl. Поля ofs і segm структури adr задають зміщення і сегмент потрібної фізичної адреси.
Оскільки в far-вказівниках сегментна компонента займає два старші байти, а зміщення-два молодші, то поля структури adr треба записувати у відповідному порядку: спочатку зміщення, а потім сегмент адреси.
Зміна значень полів структури adr викликає відповідну зміну значення кожного з вказівників, оголошених у полях об'єднання uptc.
Наприклад, наступні присвоєння:
uptc.adr.segm = 0x0; /* сегмент адреси */
uptc.adr.ofs = 0x0450; /* зміщення адреси */
сформують адресу 0000:0450h, починаючи з якої в оперативній пам'яті зберігається інформація BIOS про поточні координати текстового курсора для кожної з восьми сторінок відеопам'яті. Тепер до цих BIOS-даних можна звертатись побайтово, використовуючи вказівник pb, двобайтовими словами через вказівник pw або чотирибайтовими словами через вказівник рl. Зокрема, поточну позицію курсора на нульовій (основній) відеосторінці можна отримати так:
cur_x = *uptc.pb; /* горизонтальна координата */
cur_y = * (uptc.pb+1); /* вертикальна координата */
або можна зчитати відразу обидві координати:
cur_pos = *uptc.pw; /* читання двобайтового слова */
Використання far-вказівників пов'язане з певними проблемами, про які необхідно пам'ятати, розробляючи програми з цими вказівниками.
По-перше, одну і ту ж фізичну адресу можна задати різними значеннями вказівника. Обидва оголошені вказівники:
unsigned far* adr1= (unsigned far*)0x00000410;
unsigned far* adr2= (unsigned far*)0x00410000;
задають спільну адресу початку області даних відеопараметрів BIOS. Проте в разі порівняння цих вказівників: adr1==adr2 - результат буде хибним, бо порівнюються числові значення, записані в adr1 та adr2. А в разі порівняння far-вказівників операціями <, <=, >, >= перевіряються тільки зміщення, тобто молодші половини адрес.
Другою проблемою є міжсегментний перехід. Якщо до адреси far-вказівника додати певне число, то змінюється тільки зміщення адреси, а перенесення в сегментну половину не відбувається.
Вказаних недоліків не мають вказівники з модифікатором huge. В операціях з цими вказівниками беруть участь усі 32 розряди їхніх значень.
Після кожної операції виконується нормалізація вказівника: значення сегмента коректується так, щоб значення зміщення не перевищувало 15.
Наприклад, адреса 8C65:12A4h у нормалізованій формі записується так: 8D8F:0004h. Нормалізація забезпечує однозначність запису кожної фізичної адреси й коректність усіх операцій порівняння та адресної арифметики, в тому числі пов'язаних із міжсегментними переходами.
Тому для безпомилкової роботи з блоками даних, обсяги яких перевищують обсяг сегмента, треба використовувати huge-вказівники, хоча при цьому дещо збільшується час виконання програми через додаткові операції нормалізації.
Наступна функція застосовує huge-вказівник для визначення суми всіх машиних слів адресного простору BIOS, починаючи з адреси F000:0000h до FFFF:000Fh включно; сума є унікальною для кожної серії персональних комп'ютерів і може бути використана в програмах захисту інформації.
/* Функція обчислення суми слів BIOS-області. Варіант 1 */
unsigned long Summa_BIOS (void)
{
unsigned long sum=0;
unsigned huge* ph =(unsigned huge*)0xf0000000; /* початок області */
while (ph) /* остання адреса - ffff:000fh */
sum += *ph++;
return sum;
}
cs-, ds-, _ss- та _es-вказівники
Ефективність безпосереднього програмування оперативної пам'яті зростає у разі застосування спеціальних коротких вказівників, що оголошуються через модифікатори _cs, _ds, _ss та _es (ці специфікатори теж є службовими словами Borland C).
Короткі вказівники зберігають тільки зміщення фізичних адрес, а сегментна частина адреси береться з відповідного системного регістра: CS, DS, SS чи ES.
Для запису в сегментні регістри необхідної адреси початку сегмента використовують спеціальні псевдорегістрові змінні: _CS, _DS, _SS та _ES. Нижче подано альтернативний варіант функції обчислення суми машинних слів адресного простору BIOS, в якому для звертання до даних використано спеціальний короткий вказівник _es.
Час виконання даного варіанта функції істотно менший, ніж попереднього, де використовувався huge-вказівник.
/* Функція обчислення суми слів BIOS-області. Варіант 2 */
unsigned long BIOS_summa(void)
{
unsigned _es * p_es = (unsigned _es*)0 /* початкове зміщення */
unsigned long sum;
_ES=0xf000; /* сегмент адреси BIOS-області */
for (sum=*p_es++; *p_es; p_es++)
sum += *p_es; /* сума даних усього сегмента */
return sum;
}
Застосування спеціальних near-вказівників та відповідних псевдорегістрових змінних вимагає особливої обережності, оскільки в процесі виконання програми значення сегментних регістрів можуть змінюватись.
У цьому випадку, щоб уникнути формування помилкових фізичних адрес, перед наступним звертанням до даних через near-вказівники з модифікаторами _cs, _ds, _es або _ss необхідно відновити потрібне значення відповідного сегментного регістра. Нижче наведено ще два приклади застосування _es-вказівників у функціях зміни та копіювання текстових відеозображень.