русс | укр

Мови програмуванняВідео уроки php mysqlПаскальСіАсемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование


Linux Unix Алгоритмічні мови Архітектура мікроконтролерів Введення в розробку розподілених інформаційних систем Дискретна математика Інформаційне обслуговування користувачів Інформація та моделювання в управлінні виробництвом Комп'ютерна графіка Лекції


Арифметика адрес


Дата додавання: 2014-11-28; переглядів: 873.


Маючи p як покажчик на певний елемент масиву, p++ здійснить приріст p, тож він вказуватиме на наступний елемент, тоді як p+=n збільшить його настільки, що він вказуватиме на n елементів далі того місця, на яке посилається в дану мить. Ці і подібні конструкції являються найпростішою формою адресної або покажчикової арифметики.

C є послідовною і систематичною стосовно свого підходу до арифметики адрес; інтеграція покажчиків, масивів і адресної арифметики є однією із сильних сторін мови. Проілюструймо це шляхом написання рудиментарного розподільника пам'яті. Ми створимо дві функції. Перша,alloc(n), повертає покажчик на n-нне послідовне положення знака, яке викликаюча allocфункція може використати для збереження символів. Наступна, afree(p), звільняє отримане місце зберігання, тож його можна використати пізніше. Ці рутини «рудиментарні», оскільки виклик afree потрібно здійснювати у протилежній послідовності від викликів alloc. Тобто, місце збереження, яким керують alloc із afree, являється стеком, або ж «останній всередину — перший назовні». Стандартна бібліотека передбачає аналогічну функцію під назвою malloc іfree, на котрі не накладено таких обмежень; у Розділі 8.7 ми покажемо, як їх можна реалізувати.

Найлегше втілення — змусити alloc надати, розбитий на частини, великий символьний масив, який ми назвемо allocbuf. Цей масив буде приватним для alloc і afree. Оскільки функції мають справу з покажчиками, а не індексами масивів, жодна інша функція не повинна знати назву масиву, який можна оголосити статичним, static, у вихідному файлі, в якому містятьсяalloc з afree — це зробить його невидимим ззовні. У реальному житті, масив може навіть не мати назви, його можна було би отримати викликом malloc або спитавши операційну систему надати покажчик на безіменний блок пам'яті.

Інша важлива інформація, це скільки саме allocbuf зайнято. Ми використали покажчик під назвою allocp, який вказує на наступний вільний елемент. Коли alloc запитують розмістити nзнаків, вона перевіряє, чи залишилось достатньо місця в allocbuf. Якщо так, alloc повертає поточне значення allocp (тобто початок вільного місця), після чого збільшує його на n, для вказівки на наступну вільну ділянку. Якщо ж місця не залишилось, alloc поверне нуль. Функція afree(p) всього-навсього встановлює allocp у p, якщо p знаходиться всерединіallocbuf.

Перед викликом alloc:

allocp:

|

+-----+-------+--+-----+v---------------------------+

allocbuf: | | | | | |

+-----+-------+--+-----+----------------------------+

<------використане-----><----------вільне----------->

після виклику alloc:

allocp:

|

+-----+-------+--+-----+-------+v-------------------+

allocbuf: | | | | | | |

+-----+-------+--+-----+-------+--------------------+

<----------використане---------><------вільне------->

 

 

#define ALLOCSIZE 10000 /* розмір наявного місця */

 

static char allocbuf[ALLOCSIZE]; /* пам'ять для alloc */

static char *allocp = allocbuf; /* наступна вільна позиція */

 

char *alloc(int n) /* повертає покажчик на n знаків */

{

if (allocbuf + ALLOCSIZE - allocp >= n) { /* чи вміщається */

allocp += n;

return allocp - n; /* старий покажчик */

} else /* якщо недостатньо місця */

return 0;

}

 

void afree(char *p) /* звільняє місце, на яке вказує p */

{

if (p >= allocbuf && p < allocbuf + ALLOCSIZE)

allocp = p;

}

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

static char *allocp = allocbuf;

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

static char *allocp = &allocbuf[0];

оскільки назва масиву — це адреса елемента з індексом нуль. Умова

if (allocbuf + ALLOCSIZE - allocp >= n) { /* чи вміщається */

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

Покажчики і цілі не являються взаємозамінними. Нуль — єдине виключення. Константу нуль можна присвоїти покажчику, і сам покажчик можна порівнювати з константою-нулем. Часто замість нуля використовується символічна константа NULL, просто як мнемоніка, щоб підкреслити, що це спеціальне значення для покажчика. NULL означено в <stdio.h>. Надалі, ми також вживатимемо NULL.

Перевірки на зразок

if (allocbuf + ALLOCSIZE - allocp >= n) { /* вміщається */

та

if (p >= allocbuf && p < allocbuf + ALLOCSIZE)

демонструють декілька важливих рис арифметики покажчиків. Перш за все, покажчики можна порівнювати за певних умов. Якщо p із q вказують на члени того самого масиву, тоді порівнювання на кшталт ==, !=, <, >= тощо працюють як слід. Наприклад,

p < q

справджується, якщо p вказує на попередній до q елемент. Будь-який покажчик можна порівнювати на рівність чи не-рівність нулю. Але поводження буде невизначеним, якщо порівнювати покажчики, що не являються членами того самого масиву. (Існує один виняток: адреса першого елемента за межами масиву, яку теж можна використати в покажчиковій арифметиці.)

Друге, на що ми звернули увагу, що покажчики та цілі можна додавати та віднімати. Конструкція

p + n

означає «адреса n-ного відносно того, на який зараз вказує p». Це є чинним, незалежно від типу об'єкту на який вказує p; n збільшується в масштабі, згідно розмірові об'єкту на який вказує p, що, в свою чергу, визначається оголошенням p. Якщо int дорівнює чотирьом байтам, наприклад, int буде збільшено в масштабі в чотири рази.

Чинним є також віднімання покажчиків; якщо p та q вказують на елементи того самого масиву іp<q, тоді q-p+1 дорівнює кількості елементів від p до q, включно. Цей факт можна використати для написання іншої версії strlen:

/* strlen: повертає довжину ланцюжка s */

int strlen(char *s)

{

char *p = s;

 

while (*p != '\0')

p++;

return p - s;

}

В оголошенні, p ініційовано як s, тобто вказує на перший символ ланцюжка. Всередині циклуwhile, кожний символ перевіряється по черзі доти, поки не буде знайдено '\0' в кінці. Оскільки p вказує на символи, p++ просуває p до наступного символу кожного разу, а p-s дає кількість пройдених символів, себто довжину ланцюжка. (Кількість символів ланцюжка може бути завеликою для збереження в int. Заголовок <stddef.h> визначає тип ptrdiff_t, достатньо великий для збереження різниці значень двох покажчиків у вигляді числа зі знаком. Якщо бути ще обережнішим, можна використати size_t для значення, повернутого strlen, щоб збігалося із версією зі стандартної бібліотеки. Тип size_t є беззнаковим цілим, яке повертає оператор sizeof.

Арифметика покажчиків є досить послідовною: якщо би ми мали справу із числами з рухомою точкою (float), що займають більше місця ніж char, і якби p було покажчиком на таке число із рухомою точкою, p++ також би просунулось до наступного числа із рухомою точкою. Таким чином, ми могли би написати іншу версію alloc, яка би підтримувала числа із рухомою точкою замість символів (char), просто замінюючи char на float скрізь в alloc і afree. Всі операції з покажчиками автоматично беруть до уваги розмір об'єкту на який вказує покажчик.

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


<== попередня лекція | наступна лекція ==>
Покажчики та масиви | Покажчики на символи та функції


Онлайн система числення Калькулятор онлайн звичайний Науковий калькулятор онлайн