Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Покажчики




Тип перерахування enum

При написанні програм часто виникає необхідність визначити декілька іменованих констант, для яких потрібно, щоб всі вони мали різні значення (при цьому конкретні значення можуть бути не важливими). Для цього зручно скористатися типом даних "перерахування" enum (enumeration), всі можливі значення якого задаються списком цілочисельних констант.
Синтаксис:
enum [ ім'я_типу ] { список_констант };
Ім'я типу задається тоді, коли в програмі є необхідність визначати змінні даного типу. Компілятор забезпечує, щоб ці змінні приймали значення тільки із вказаного списку констант.
enum {mRead, mEdit, mWrite, mCreate } Mode;
Цей оператор вводить іменовані константи mRead, mEdit, mWrite і змінну Mode, яка може приймати значення цих констант. В момент оголошення змінна ініціалізується значенням першої константи, в наведеному прикладі - mRead. В подальшому їй можна присвоювати будь-які допустимі значення. Наприклад:
Mode = mCreate;
Значення змінної типу перерахування можна перевіряти, порівнюючи її з можливими значеннями. Крім того, потрібно враховувати, що типи перерахування відносяться до цілих порядкових типів і до них можуть бути застосовані будь-які операції порівняння. Наприклад:
if (Mode>mRead) /* … */;
Змінну Mode можна також використовувати в структурі switch:
switch(Mode)
{
case mRead: /* … */
break;
case mEdit: /* … */
break;
case mWrite: /* … */
break;
case mCreate: /* … */
break;
}
По замовчуванню значення, які вказані в enum, інтерпретуються як цілі числа, причому перше значення рівне 0, друге - 1 і т.д. Значення по замовчанню можна змінити, якщо після імені константи вказати знак рівності і задати ціле значення константи. Наприклад:
enum {mRead = -1, mEdit, mWrite = 2, mCreate } Mode;
Якщо після констант не задане їх ціле значення, воно вважається на 1 більшим, ніж попереднє. Тому для нашого прикладу значення констант такі:

mRead -1
mEdit  
mWrite  
mCreate  

1.8.1 Основні відомості про покажчики
В результаті процесу компіляції програми всі імена змінних будуть перетворені в адреси комірок пам'яті, в яких містяться відповідні значення даних. У командах машинної програми при цьому знаходяться машинні адреси розміщення значень змінних. Саме це і є пряма адресація - виклик значення за адресою в команді. Наприклад, в операторі присвоювання: k = j на машинному рівні відбувається копіювання значення з області ОП, що відведена змінній j, в область ОП, яка відведена змінній k. Таким чином, при виконанні машинної програми реалізуються операції над операндами - значеннями змінних, розташованими за визначеними адресами ОП. На машинному рівні імена змінних у командах не використовуються, а тільки адреси, сформовані транслятором з використанням імен змінних. Проте програміст не має доступу до цих адрес, якщо він не використовує покажчики.
Покажчики в Сі використовується набагато інтенсивніше, аніж, скажімо, у Паскалі, тому що іноді деякі обчислення виразити можливо лише за їх допомогою, а частково й тому, що з ними утворюються більш компактні та ефективніші програми, аніж ми використовували б звичайні засоби. Навіть існує твердження - аби стати знавцем Сі, потрібно бути спеціалістом з використання покажчиків.
Покажчик (вказівник) - це змінна або константа стандартного типу даних для збереження адреси змінної визначеного типу. Значення покажчика - це беззнакове ціле, воно повідомляє, де розміщена змінна, і нічого не говорить про саму змінну.
Тип змінної, що адресується, може бути стандартний, нумерований, структурний, об'єднання або void. Покажчик на тип void може адресувати значення будь-якого типу. Розмір пам'яті для самого покажчика і формат збереженої адреси (вмісту покажчика) залежить від типу комп'ютера та обраної моделі пам'яті. Константа NULL зі стандартного файлу stdio.h призначена для ініціалізації покажчиків нульовим (незайнятим) значенням адреси.
Змінна типу покажчик оголошується подібно звичайним змінним із застосуванням унарного символу "*". Форма оголошення змінної типу покажчик наступна:
тип [модифікатор] * імені-покажчика;
де тип - найменування типу змінної, адресу якої буде містити змінна-покажчик (на яку він буде вказувати).
Модифікатор необов'язковий і може мати значення:
near - ближній, 16-бітний покажчик (встановлюється за замовчуванням), призначений для адресації 64-кілобайтного сегмента ОП;
far - дальній, 32-бітний покажчик, містить адресу сегмента і зсув у ньому: може адресувати ОП обсягом до 1 Мб;
huge - величезний, аналогічний покажчику типу far, але зберігається у нормалізованому форматі, що гарантує коректне виконання над ним операцій; застосовується до функцій і до покажчиків для специфікації того, що адреса функції або змінної, що адресується, має тип huge;
• імені-покажчика - ідентифікатор змінної типу покажчик;
• визначає змінну типу покажчик.
Значення змінної-покажчика - це адреса деякої величини, ціле без знака. Покажчик містить адресу першого байту змінної визначеного типу. Тип змінної, що адресується, і на яку посилається покажчик, визначає об'єм ОП, що виділяється змінній, та зв'язаному з нею покажчикові. Для того, щоб машинною програмою обробити (наприклад, прочитати або записати) значення змінної за допомогою покажчика, треба знати адресу її початкового (нульового) байта та кількість байтів, що займає ця змінна. Покажчик містить адресу нульового байту цієї змінної, а тип змінної, що адресується, визначає, скільки байтів, починаючи з адреси, визначеної покажчиком, займає це значення.
Нижче наведено приклади деяких можливих оголошень покажчиків:
int *pi; /* - покажчик - змінна на дані типу int */
float *pf; /* - покажчик - змінна на дані типу float */
int ml [5]; /* - ім'я масиву на 5 значень типу int; ml - покажчик-константа, про це йтиметься згодом */
int *m2[10]; /* m2 - ім'я масиву на 10 значень типу покажчик на значення типу int, m2 - покажчик-константа */
int (*m3)[10]; /* - покажчик на масив з 10 елементів типу int; m3 - покажчик-константа */
Зверніть увагу на те, що у трьох з наведених оголошень ім'я масиву є константою - покажчиком! (Про це йтиметься в наступному окремому розділі.)
За допомогою покажчиків, наприклад, можна:
1. обробляти одновимірні та багатовимірні масиви, рядки, символи, структури і масиви структур;
2. динамічно створювати нові змінні в процесі виконання програми;
3. обробляти зв'язані структури: стеки, черги, списки, дерева, мережі;
4. передавати функціям адреси фактичних параметрів;
5. передавати функціям адреси функцій в якості параметрів.
Протягом довгого часу програмісти були незадоволені покажчиками. Зокрема, застосування покажчиків критикується через те, що в силу їх природи неможливо визначити, на яку змінну вказує в даний момент покажчик, якщо не повертатися до того місця, де покажчику востаннє було присвоєно значення. Це ускладнює програму і робить доведення її правильності дещо ускладненим. Програміст, що добре володіє Сі, повинен насамперед знати, що таке покажчики, та вміти їх використовувати. Практично у програмі можна використовувати не імена змінних, а тільки покажчики, тобто адреси розміщення змінних програми.

1.8.2 Моделі пам'яті
У мові Сі для операційної системи MS-DOS розмір ОП (оперативної пам'яті) для розміщення покажчика залежить від типу використаної моделі пам'яті. У програмах на мові Сі можна використовувати одну з шести моделей пам'яті: крихітну (tiny), малу (small, по замовчуванню), середню (medium), компактну (compact), велику (large) і величезну (huge).
Взагалі оперативна пам'ять для виконання програми на мові Сі використовується для:
• розміщення програми (коду програми);
• розміщення зовнішніх (глобальних) і статичних даних (що мають специфікатори extern і static, про них йтиметься нижче);
• динамічного використання ОП для змінних, сформованих у процесі виконання програми (купа, динамічна ОП, про них йтиметься нижче);
• для розміщення локальних (auto - автоматичних) змінних, змінних функцій (стек) під час виконання програми.


Рис. 1.10. Структура оперативної пам'яті

ОП програми та її статичних даних у процесі виконання програми залишається незмінною. ОП з купи виділяється та звільняється в процесі виконання програми. Об'єм ОП для купи залежить від того, скільки ОП запитує програма за допомогою функцій calloc () та malloc () для динамічного розміщення даних. Пам'ять стека виділяється для фактичних параметрів активізованих функцій і їх локальних (автоматичних) змінних. Розглянемо основні характеристики різних моделей ОП.
Крихітна (tiny model) ОП. Модель пам'яті використовується при дефіциті ОП. Для коду програми, статичних даних, динамічних даних (купи) та стеку виділяється 64 Кб. Змінна - покажчик типу near (ближній) займає 2 байти.
Мала (small model) ОП. Для програми призначається 64 Кб. Стек, купа і статичні дані займають по 64 Кб. Ця модель приймається по замовчуванню та використовується для вирішення маленьких і середніх задач. Покажчик типу near займає 2 байти і містить адресу - зсув усередині сегмента ОП з 64 Кб.
Середня (medium model) ОП. Розмір ОП для програми дорівнює 1 Мбайт. Стек, купа і статичні дані розміщаються в сегментах ОП розміром 64 Кб. Цю модель застосовують для дуже великих програм і невеликих обсягів даних. Покажчик у програмі типу far займає 4 байти. Для адресації даних покажчик типу near займає 2 байти.
Компактна (compact model) ОП. Для програми призначається 64 Кб. Для даних - 1 Мбайт. Об'єм статичних даних обмежується 64 Кб. Розмір стека повинен бути не більш 64 Кб. Ця модель використовується для малих і середніх програм, що вимагають великого об'єму даних. Покажчики в програмі складаються з 2 байтів, а для даних - з 4 байтів.
Велика (large model) ОП. ОП для програми обмежена 1 Мб. Для статичних даних призначається 64 Кб. Купа може займати до 1 Мб. Програма і дані адресуються покажчиками, що займають 4 байти. Модель використовується для великих задач. Окрема одиниця даних, наприклад масив, повинна займати не більш 64 Кб.
Величезна (huge model) ОП. Аналогічна великій моделі. Додатково в ній знімається обмеження на розмір окремої одиниці даних.

1.8.3 Основні операції над покажчиками
Мова Сі надає можливість використання адрес змінних програми за допомогою основних операцій - & та *:
За допомогою основних операцій можна отримати значення адреси змінної а використовуючи непряму адресацію - одержати значення змінної за її адресою.
Призначення цих операцій:
& ім'я змінної - одержання адреси; визначає адресу розміщення значення змінної визначеного типу;
* ім'я-покажчика - отримання значення визначеного типу за вказаною адресою; визначає вміст змінної, розміщеної за адресою, що міститься у даному покажчику; це - непряма адресація (інші назви - "зняття значення за покажчиком" або "розіменування").
Оператор присвоювання значення адреси покажчику має вигляд:
Ім'я_змінної_покажчика = & ім'я змінної;
Наприклад:
int i, *pi; /* pi -змінна покажчик */
pi = &i; /* pi одержує значення адреси 'i' */
Операція & - визначення адреси змінної повертає адресу ОП свого операнда. Операндом операції & повинне бути ім'я змінної того ж типу, для якого визначений покажчик лівої частини оператора присвоювання, що одержує значення цієї адреси. У вищенаведеному прикладі це тип int.
Операції * і & можна писати впритул до імені операнду або через пробіл. Наприклад: &і, * pi.
Непряма адресація змінної за допомогою операції * здійснює доступ до змінної за покажчиком, тобто повернення значення змінної, розташованої за адресою, що міститься у покажчику. Операнд операції * обов'язково повинен бути типу покажчик. Результат операції * - це значення, на яке вказує (адресує, посилається) операнд. Тип результату - це тип, визначений при оголошенні покажчика.
У загальному вигляді оператор присвоювання, що використовує ім'я покажчика та операцію непрямої адресації, можна представити у вигляді:
ім'я змінної * ім'я-покажчика;
де ім'я-покажчика - це змінна або константа, що містить адресу розміщення значення, необхідного для змінної лівої частини оператора присвоювання.
Наприклад:
i= *pi; /* 'i' одержує значення, розташоване за адресою, що міститься в покажчику 'pi' */
Як і будь-які змінні, змінна pi типу покажчик має адресу і значення. Операція & над змінною типу покажчик: &pi - дає адресу місця розташування самого покажчика, pi - ім'я покажчика визначає його значення, a *pi - значення змінної, що адресує покажчик.
Звичайно, усі ці значення можна надрукувати. Наприклад, за допомогою наступної програми:
#include <stdio.h>
void main()
{
char c = 'A';
int i = 7776;
int *pi = &i;
char *pc = &c;
printf ("pi=%u,*pi=%d, &pi=%u\n", pi, *pi, &pi);
printf ("pc=%u, *pc=%c, &pc=%u\n", pc, *pc, &pc);
}
У результаті виконання буде виведено:
pi = 65522, *pi = 7776, &pi = 65520
pc = 65525, *рс = А, &pc = 65518
Одне з основних співвідношень при роботі з покажчиками - це симетричність операцій адресації та непрямої адресації. Вона полягає в тому, що:
&х == х, тобто вміст за адресою змінної х є значення х. Наприклад, оголошення покажчика pi і змінних i та j:
int *pi, i = 123, j;
pi = &i; /*-присвоювання покажчику значення адреси i */
j = *pi; /* - присвоювання j вмісту за адресою pi */
Тут змінна j отримує вміст, розташований за адресою змінної i, тобто значення змінної, що адресує покажчик pi: j = * pi = * &i = i;. Два останніх вищенаведених оператора виконують те саме, що один оператор: j = i.
Для повного остаточного розуміння процесів, що відбувається у пам'яті при маніпуляції з покажчиками, розглянемо ще такий фрагмент:
void func()
{
int х;
int *pх; /* pх - покажчик на змінну типу int*/
pх= &х; /* адреса змінної х заноситься в рх*/
*pх=77; /* число зберігається за адресою, на яку вказує рх */
}

Розглянемо цей приклад на конкретному малюнку: функція займає область пам'яті, починаючи з адреси 0х100, х знаходиться за адресою 0х102, а рх - 0х106. Тоді перша операція присвоювання, коли значення &х(0х102) зберігається в рх, матиме вигляд, зображений на рис. 1.11 зліва:
Наступну операцію, коли число 77 записується за адресою, яка знаходиться в рх та дорівнює 0х102 (адреса х), відображає рис. 1.11 справа. Запис *рх надає доступ до вмісту комірки, на яку вказує рх.

Далі наведений приклад програми виводу значень покажчика і вмісту, розташованого за адресою, що він зберігає.
#include<stdio.h>
void main()
{
int i = 123, *pi = &i; /* pi-покажчик на значення типу int */
printf("розмір покажчика pi = %d\n", sizeof(pi));
printf("адреса розміщення покажчика pi=%u\n", &pi);
printf("адреса змінної i = %u\n", &i);
printf("значення покажчика pi = %u\n", pi);
printf("значення за адресою pi = %d\n", *pi);
printf("значення змінної i = %d\n", i);
}
Результати виконання програми:
розмір покажчика pi = 2
адреса розміщення покажчика pi = 65522
адреса змінної i= 65524
значення покажчика pi = 65524
значення за адресою pi = 123
значення змінної i = 123

Покажчики можна використовувати:
1. у виразах, наприклад, для одержання значень, розташованих за адресою, що зберігається у покажчику;
2. у лівій частині операторів присвоювання, наприклад:
a. для одержання значення адреси, за якою розташоване значення змінної;
b. для одержання значення змінної.
Наприклад, якщо pi - покажчик цілого значення (змінної i), то *pi можна використовувати в будь-якому місці програми, де можна використовувати значення цілого типу. Наприклад:
int i = 123, j, *pi;
pi = &i; /*pi у лівій частині оператора присвоювання */
j = *pi + 1; /*-це еквівалентно: j = i + 1; pi-у виразі правої частини оператора присвоювання*/
Виклик значення за покажчиком можна використовувати також як фактичні параметри при звертанні до функцій. Наприклад:
d = sqrt ((double) *pi); /* *pi - фактичний параметр */
fscant (f, "%d", pi); /* pi - фактичний параметр */
printf ("%d\n", *pi); /* *pi - фактичний параметр */
У виразах унарні операції & і *, пов'язані з покажчиками, мають більший пріоритет, ніж арифметичні. Наприклад:
*рх = &х;
у = 1 + *рх; /*-спочатку виконується '*', потім '+' */
Останній оператор еквівалентний:
у = 1 + х;
Для звертання до значення за допомогою покажчика-змінної його можна використовувати в операторі присвоювання скрізь, де може бути ім'я змінної. Наприклад, після виконання оператора: рх = &х; цілком еквівалентними є такі описи:
Оператор: Його еквівалент: Або:
*рх =0; х = 0;
*рх += 1; *рх = *рх + 1; х = х + 1;
(*рх)++; *рх = *рх + 1; х = х + 1;
(*рх)--; *рх = *рх - 1; х = х - 1;

Наступна програма демонструє найпростіше практичне використання покажчиків, виводячи звичайну послідовність літер алфавіту:
#include <stdio.h>
char c; /* змінна символьного типу*/
main()
{
char *pc; /* покажчик на змінну символьного типу*/
pc=&c;
for(c='A';c<='Z';c++)
printf("%c",*pc);
return 0;
}
У операторі printf("%c",*pc) має місце розіменування покажчика (*рс) - передача у функцію значення, що зберігається за адресою, яка міститься у змінній рс. Щоб дійсно довести, що рс є псевдонімом с, спробуємо замінити *рс на с у виклику функції - і після заміни програма працюватиме абсолютно аналогічно. Оскільки покажчики обмежені заданим типом даних, типовою серйозною помилкою їх використання буває присвоєння адреси одного типу даних покажчика іншого типу, на що компілятор реагує таким чином:
"Suspicious pointer conversion in function main()"
На ТС це лише попередження (підозріле перетворення покажчика у функції main()(?!)), і якщо на нього ніяк не відреагувати, то програма працюватиме й надалі (адже помилку зафіксовано не буде) і залишається лише здогадуватися, який результат буде надалі. Зазначимо, що компілятор BС++ з приводу такого "підозрілого перетворення" пішов все-таки далі: він просто відмовляється працювати, видаючи повідомлення про помилку. Відповідальність за ініціалізацію покажчиків повністю покладається на програміста, і більш детально про це йтиметься далі.

1.8.4 Багаторівнева непряма адресація
У мові Сі можна використовувати багаторівневу непряму адресацію, тобто непряму адресацію на 1, 2 і т.д. рівні. При цьому для оголошення і звертання до значень за допомогою покажчиків можна використовувати відповідно кілька символів зірочка: *. Зірочки при оголошенні ніби уточнюють призначення імені змінної, визначаючи рівень непрямої адресації для звертання до значень за допомогою цих покажчиків. Приклад оголошення змінної і покажчиків для багаторівневої непрямої адресації можна привести наступний:
int i = 123 /* де: i - ім'я змінної */
int *pi = &i; /* pi - покажчик на змінну і */
int **ppi = &pi; /* ppi - покажчик на покажчик на змінну pi */
int ***pppi = &ppi; /* pppi - покажчик на 'покажчик на 'покажчик на змінну ppi' */
Для звертання до значень за допомогою покажчиків можна прийняти наступне правило, що жорстко зв'язує форму звертання з оголошенням цих покажчиків:
• повна кількість зірочок непрямої адресації, рівна кількості зірочок при оголошенні покажчика, визначає значення змінної;
• зменшення кількості зірочок непрямої адресації додає до імені змінної слово "покажчик", причому цих слів може бути стільки, скільки може бути рівнів непрямої адресації для цих імен покажчиків, тобто стільки, скільки зірочок стоїть в оголошенні покажчика.
Наприклад, після оголошення:
int i, *pi=&i;
звертання у виді:
*pi - визначає значення змінної,
pi - покажчик на змінну i.
А при звертанні до змінних можна використовувати різну кількість зірочок для різних рівнів адресації:
pi, ppi, pppi - 0-й рівень адресації, пряма адресація;
*pi, *ppi, *pppi - 1-й рівень непрямої адресації
**ppi, **pppi - 2-й рівень непрямої адресації
***pppi - 3-й рівень непрямої адресації
Таким чином, до покажчиків 1-го і вище рівнів непрямої адресації можливі звертання і з меншою кількістю зірочок непрямої адресації, аніж задано при оголошенні покажчика. Ці звертання визначають адреси, тобто значення покажчиків визначеного рівня адресації. Відповідність між кількістю зірочок при звертанні за допомогою покажчика і призначенням звертання за покажчиком для наведеного прикладу ілюструє таблиця 1.12 (де Р.н.а. - рівень непрямої адресації):
Таблиця 1.12. Відповідність між кількістю уточнень (*) і результатом звертання за допомогою покажчика

Звертання Результат звертання Р.н.а.
i значення змінної i  
*pi pi значення змінної, на яку вказує pi покажчик на змінну типу int, значення pi 1 0
**ppi *ppi ppi значення змінної типу int покажчик на змінну типу int покажчик на "покажчик на змінну типу int', значення покажчика ppi 2 1 0
***pppi **pppi *pppi pppi значення змінної типу int; покажчик на змінну типу int покажчик на 'покажчик на змінну типу int' покажчик на 'покажчик на 'покажчик на змінну типу int', значення покажчика pppi 3 2 1 0

1.8.5 Операції над покажчиками
Мова Сі надає можливості для виконання над покажчиками операцій присвоювання, цілочисельної арифметики та порівнянь. Мовою Сі можливо:
1. присвоїти покажчику значення адреси даних, або нуль;
2. збільшити (зменшити) значення покажчика;
3. додати або відняти від значення покажчика ціле число;
4. скласти або відняти значення одного покажчика від іншого;
5. порівняти два покажчики за допомогою операцій відношення.
Змінній-покажчику можна надати певне значення за допомогою одного із способів:
1. присвоїти покажчику адресу змінної, що має місце в ОП, або нуль, наприклад:
pi = &j;
pi = NULL;
2. оголосити покажчик поза функцією (у тому числі поза main ()) або у будь-якій функції, додавши до нього його інструкцію static; при цьому початковим значенням покажчика є нульова адреса (NULL);
3. присвоїти покажчику значення іншого покажчика, що до цього моменту вже має визначене значення; наприклад:
pi = pj; це - подвійна вказівка однієї і тієї ж змінної;
4. присвоїти змінній-покажчику значення за допомогою функцій calloc () або malloc () - функцій динамічного виділення ОП.
Усі названі дії над покажчиками будуть наведені у прикладах програм даного розділу. Розглянемо кілька простих прикладів дій над покажчиками.
Зміну значень покажчика можна робити за допомогою операцій: +, ++, -, --. Бінарні операції (+ та -) можна виконувати над покажчиками, якщо обидва покажчики посилаються на змінні одного типу, тому що об'єм ОП для різних типів даних може вирізнятися.
Наприклад, значення типу int займає 2 байти, а типу float - 4 байти. Додавання одиниці до покажчика додасть "квант пам'яті", тобто кількість байтів, що займає одне значення типу, що адресується. Для покажчика на елементи масиву це означає, що здійснюється перехід до адреси наступного елемента масиву, а не до наступного байта. Тобто значення покажчика при переході від елемента до елемента масиву цілих значень буде збільшуватися на 2, а типу float - на 4 байти. Результат обчислення покажчиків визначений у мові Сі як значення типу int.
Приклад програми зміни значення покажчика на 1 квант пам'яті за допомогою операції "++" і визначення результату обчислення покажчиків даний на такому прикладі:
#include<stdio.h>
void main ()
{
int a[] = { 100, 200, 300 };
int *ptr1, *ptr2;
ptr1=a; /*- ptrl одержує значення адреси а[0] */
ptr2 = &а[2]; /*- ptr2 одержує значення адреси а[2] */
ptr1++; /* збільшення значення ptrl на квант ОП:
ptr1 = &а[1]*/
ptr2++; /* збільшення значення ptr2 на квант ОП:
ptr2 = &а[3]*/
printf (" ptr2 - ptr1 = %d\n", ptr2 - ptr1);
}

Результат виконання програми:
ptr2 - ptr1 = 2
Результат 2 виконання операції віднімання визначає 2 кванти ОП для значень типу int:
ptr2 - ptr1 = &а[3] - &а[1] = (а + 3) - (а + 1) = 2;
У наступному Сі-фрагменті продемонстрований приклад програми для виведення значень номерів (індексів) елементів масивів, адрес першого байта ОП для їх розміщення та значень елементів масивів. Справа в тому, що в Сі є дуже важлива властивість - ім'я масиву еквівалентно адресу його нульового елемента: х == &х[0]. Покажчики pi і pf спочатку містять значення адрес нульових елементів масивів, а при виведенні складаються з i-номером елемента масиву, визначаючи адресу i-елемента масиву. Для одержання адрес елементів масивів у програмі використовується додавання покажчиків-констант х та у, та змінних-покажчиків pi і pf з цілим значенням змінної i. Зміна адрес у програмі дорівнює кванту ОП для даних відповідного типу: для цілих - 2 байти, для дійсних - 4 байти.
#include<stdio.h>
void main()
{
int x[4], *pi = х, i;
float y[4], *pf = y;
printf("\nномер елемента адреси елементів масивів:\n i pi+i х + i &x[i] pf+i у+i &y[i]\n");
for (i = 0; i < 4; i++)
printf(" %d: %6u %6u %6u %6u %6u %6u\n", i, pi + i, x + i, &x[i], pf + i, y + i, &y[i]);
}
Результати виконання програми:

номер елемента адреси елементів масивів:

i pi+i х+i &x[i] pf+i y+i &y[i]
0:            
1:            
2:            
3:            


Мовою Сі можна визначити адреси нульового елемента масиву х як х або &х[0]: х == &х[0]. Краще і стисло використовувати просто х - це базова адреса масиву. Ту саму адресу елемента масиву можна представити у вигляді: х + 2 == &х[2]; х + i == &x[i].

Те саме значення можна представити у вигляді:
*(х + 0) == *х == х[0] - значення нульового елемента масиву х;
*(х + 2) == x[2] - значення другого елемента масиву х;
*(х + i) == x[i] - значення i-го елемента масиву х.
А операції над елементами масиву х можна представити у вигляді:
*х + 2== х[0] +2; *(х + i) - 3 == x[i] - 3;

1.8.6 Проблеми, пов'язані з покажчиками
Проблеми, пов'язані з покажчиками, виникають при некоректному використанні покажчиків. Усі застереження щодо некоректного використання покажчиків відносяться до мови Сі так само, як і до багатьох інших низькорівневих мов програмування. Некоректним використанням покажчиків може бути:
• спроба працювати з неініціалізованим покажчиком, тобто з покажчиком, що не містить адреси ОП, що виділена змінній;
• втрата вказівника, тобто значення покажчика через присвоювання йому нового значення до звільнення ОП, яку він адресує;
• незвільнення ОП, що виділена за допомогою функції malloc ();
• спроба повернути як результат роботи функції адресу локальної змінної класу auto (про функції та класи змінних йтиметься далі);
Запит на виділення ОП з купи робиться за допомогою функцій calloc () та malloc (). Повернення (звільнення) ОП робиться за допомогою функції free (). Розглянемо деякі проблеми, пов'язані з покажчиками.
При оголошенні покажчика на скалярне значення будь-якого типу оперативна пам'ять для значення, що адресується, не резервується. Виділяється тільки ОП для змінної-покажчика, але покажчик при цьому не має значення. Якщо покажчик має специфікатор static, то ініціюється початкове значення покажчика, рівне нулю (особливості статичних змінних, про що йтиметься в окремому розділі). Приклад ініціалізації покажчиків нульовими значеннями при їх оголошенні:
static int *pi, *pj; /* pi = NULL; pj= NULL; */
Розглянемо приклад, що містить грубу помилку: спробу працювати з непроініціалізованим покажчиком.
int *х; /* змінній-покажчику 'х' виділена ОП, але 'х' не містить значення адреси ОП для змінної */
*х = 123; /* - груба помилка! */
Таке присвоювання помилкове, тому що змінна-покажчик х не має значення адреси, за яким має бути розташоване значення змінної.
Компілятор видасть попередження:
Warning: Possible use of 'x' before definition
При цьому випадкове (непроініціалізоване) значення покажчика (сміття) може бути неприпустимим адресним значенням! Наприклад, воно може збігатися з адресами розміщення програми або даних користувача, або даних операційної системи. Запис цілого числа 123 за такою адресою може порушити працездатність програми користувача або самої OC. Компілятор не виявляє цю помилку, це повинен робити програміст!
Виправити ситуацію можна за допомогою функції malloc (). Форма звертання до функції malloc () наступна:
ім'я-покажчика = (тип-покажчика) malloc (об'єм -ОП);
де ім'я-покажчика - ім'я змінної-покажчика, тип-покажчика - тип значення, що повертається функцією malloc;
об'єм-ОП - кількість байтів ОП, що виділяються змінній, яка адресується.
Наприклад:
х = (int *) malloc (sizeof (int));
При цьому з купи виділяється 2 байти ОП для цілого значення, а отримана адреса його розміщення заноситься в змінну-покажчик х. Значення покажчика гарантовано не збігається з адресами, що використовуються іншими програмами, у тому числі програмами OС. Параметр функції malloc визначає об'єм ОП для цілого значення за допомогою функції sizeof(int). Запис (int *) означає, що адреса, що повертається функцією malloc (), буде розглядатися як покажчик на змінну цілого типу. Це операція приведення типів.
Таким чином, помилки не буде у випадку використання наступних операторів:
int *х; /* х - ім'я покажчика, він одержав ОП */
х = (int *) malloc (sizeof(int)); /* Виділена ОП цілому значенню, на яке вказує 'x' */
*х = 123; /* змінна, на яку вказує 'х', одержала значення 123*/
Повернення (звільнення) ОП у купі виконує функція free (). Її аргументом є ім'я покажчика, що посилається на пам'ять, що звільняється. Наприклад:
free (x);
Щоб уникнути помилок при роботі з функціями не слід повертати як результат їхнього виконання адреси автоматичних (локальних) змінних функції. Оскільки при виході з функції пам'ять для всіх автоматичних змінних звільняється, повернута адреса може бути використаною системою й інформація за цією адресою може бути невірною. Можна повернути адресу ОП, що виділена з купи.
Одна з можливих помилок - подвійна вказівка на дані, розташовані у купі, і зменшення об'єму доступної ОП через незвільнення отриманої ОП. Це може бути для будь-якого типу даних, у тому числі для скаляра або масиву. Розглянемо випадок для скаляра.
Приклад фрагмента програми з подвійною вказівкою і зменшенням об'єму доступної ОП через незвільнення ОП наведений нижче:
#include<alloc.h>
void main ()
{
/* Виділення ОП динамічним змінним х, у и z: */
int *х = (int *) malloc (sizeof(int)),
*у = (int *) malloc (sizeof(int)),
*z = (int *) malloc (sizeof(int));
/* Ініціалізація значення покажчиків х, у, z;*/
*х = 14; *у = 15; *z = 17;
/*Динамічні змінні одержали конкретні цілі значення*/
y=x; /* груба помилка - втрата покажчика на динамічну змінну в без попереднього звільнення її ОП */
}
У наведеному вище прикладі немає оголошення імен змінних, є тільки покажчики на ці змінні. Після виконання оператора y = х; х та у є двома покажчиками на ту саму ОП змінної *х. Тобто *х = 14; і *у = 14. Крім того, 2 байти, виділені змінній, яку адресував y для розміщення цілого значення (*у), стають недоступними (загублені), тому що значення y, його адреса, замінені значенням х. А в купі ці 2 байти для *у вважаються зайнятими, тобто розмір купи зменшений на 2 байти. Відбулося зменшення доступної ОП. Цього слід уникати.
Щоб уникнути такої помилки треба попередньо звільнити ОП, виділену змінній *у, а потім виконати присвоювання значення змінній у. Наприклад:
free (у); /* звільнення ОП, виділеної змінної '*у' */
у = х; /* присвоювання нового значення змінній 'у' */
Чи можна змінній-покажчику присвоїти значення адреси в операторі оголошення? Наприклад:
int *x = 12345;
Тут константа 12345 цілого типу, а значенням покажчика х може бути тільки адресою, покажчиком на байт в ОП. Тому компілятор при цьому видасть повідомлення про помилку:
Error PR.CPP 3: Cannot convert 'int to 'int *'
Проте не викличе помилки наступне присвоювання:
int a[5], *х = а;
Використання покажчиків часто пов'язано з використанням масивів різних типів. Кожний з типів даних масивів має свої особливості. Тому далі розглянемо властивості покажчиків для роботи з масивами.




Поделиться с друзьями:


Дата добавления: 2014-01-07; Просмотров: 928; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.007 сек.