- •Адреси та вказівники
- •Вільна пам'ять
- •Динамічні масиви
- •Поняття динамічного лінійного списку
- •If not (s належить sqs) then
- •V : string;
- •Створення та обробка списку
- •Var p : pElem; begin
- •Var f:text; {вхідний текст}
- •If not isIn(s,h) {якщо рядка в списку немає}
- •Var sqs:pElem; {вказівник на голову списку}
- •Вилучення елемента списку
- •Var p:pElem; {допоміжний вказівник}
- •Var p:pElem; {допоміжний вказівник}
Адреси та вказівники
Пам'ять комп'ютера можна розглядати як послідовність байтів з номерами 0,1,2..; ці номери називаються адресами. Кожна змінна займає залежно від її типу певну кількість послідовних байтів пам'яті.
Адреса змінної — це адреса її першого байта.
Нехай Т — деякий тип. Вираз ^Т позначає тип, значеннями якого є адреси змінних типу Т.
Наприклад, ^integer позначає тип адрес змінних типу integer, а ^аггау [ 1..100] of char — тип адрес масивів, які мають по 100 символів.
Тип ^Т називається типом адрес даних типу Т або типом посилань на тип Т, а Т — базовим для нього.
Адреса змінної х повертається з виклику функції addr вигляду addr (x) або є результатом операції @: @x.
Ім'я nil позначає адресу, яка не може бути адресою жодної змінної, тобто вона «нічийна» або неіснуюча. До однотипних адрес застосовуються операції порівняння на рівність = і нерівність < >.
Змінні, значеннями яких є адреси, називаються вказівниками. Змінна типу ^Т називається типізованим вказівником (вказівником типу Т). Вказівнику типу Т можна присвоювати адреси змінних типу Т. Коли адреса змінної присвоюється вказівнику, то кажуть, що він установлюється на змінну.
Приклад. Нехай діють такі оголошення:
type aint = array[1..5] of integer;
paint = ^aint; ppaint = ^paint;
var x : aint; p : paint; pp : ppalnt;
Вказівник p встановлюється на змінну x оператором p: = addr (x) або p: =@x, a pp на p — оператором pp: =addr (p) або рр:=@р.
Рис. 1. Установка вказівників
До вказівників застосовують операцію розіменування^: якщо р — вказівник типу ^Т, то вираз р^ задає змінну типу Т, на яку встановлено р. Отже, якщо р встановлено на змінну х, то вирази хір^ еквівалентні. У наведеному прикладі елемент масиву з індексом k позначається як виразом х[к], так і виразами р^[к] або рр^^[к], тобто присвоювання р^[1] :=1 та рр^^[1]:=1 означають те саме, що й х[1]:=1.
Розіменування вказівника зі значенням nil (вказівника, не встановленого ні на що) призводить до аварійного завершення програми.
У мові Турбо Паскаль вказівник будь-якого типу займає 4 байти, може мати 232= 4 294 967 296 різних станів і вказувати на 4 гігабайти оперативної пам'яті.
Окрім типізованих вказівників, у мові Turbo Pascal є тип Pointer, значеннями якого є довільні адреси. Цей тип сумісний за присвоюванням та порівнянням з іншими типами вказівників. Змінні типу Pointer називаються безтиповими вказівниками.
Їх використання подано нижче.
Тип адрес Pointer збільшує гнучкість і потужність мови, але знижує рівень надійності програмування.
У мові Turbo Pascal передбачено засоби явного позначення адрес, але тут вони не розглядаються.
Вільна пам'ять
Хворий, який сидів на ліжку в глибині палати, підвівся...
й мученицьким голосом закричав
— На волю! На волю! У пампаси!
Ілля Ільф, Євген Петров
Основним призначенням вказівників є робота з вільною пам'яттю.
Пам'ять процесу виконання програми поділяється на кілька різних за своїм призначенням частин:
пам'ять, яку займає код програми (виконувані команди);
пам'ять, яку займають бібліотеки, підключені до програми;
статична пам'ять (для статичних змінних програми й модулів);
автоматична пам'ять (для автоматичних змінних під час виконання викликів підпрограм);
вільна пам'ять, або купа.
Вільна пам'ять відрізняється від інших тим, що її ділянки виділяються під змінні й звільняються від них за явними вказівками у Паскаль-програмі (динамічно). Змінні в ній не мають імен у програмі, ідентифікуються за допомогою встановлених на них вказівників і називаються динамічними.
Створення та знищення динамічних змінних називається керуванням купою.
Найпростішими процедурами створення та знищення динамічних змінних є процедури new та dispose. Їх виклики мають вигляд new (р) та dispose (р), де р — типізований вказівник. Сам вказівник може бути автоматичною, статичною або динамічною змінною.
При виконанні виклику new (р), де р — вказівник типу Т, відокремлюється вільна, тобто не зайнята іншими даними, ділянка купи. її довжиною є кількість байт, які займають дані типу Т. Адреса першого байта ділянки присвоюється р, тобто р встановлюється на цю ділянку.
Наприклад, якщо змінну р оголошено як вказівник на масив із 5 цілих змінних, то результат виконання new (р) можна подати, як на рис. 2 (на с. 6). Після установки динамічна змінна позначається виразом р^.
Якщо в купі вільна ділянка потрібного розміру відсутня, виконання програми завершується аварійно. При створенні великої кількості динамічних змінних, коли є ризик вичерпати вільну пам'ять, можна уникнути аварійного завершення за допомогою функції maxavail. її виклик (без параметрів) повертає розмір найбільшої неперервної ділянки купи — значення типу longint.
Наприклад, виділяти пам'ять під дані типу Т й установлювати на неї вказівник р типу ^Т можна так:
if sizeof(Т)<=maxavail {якщо є потрібна ділянка)
then new(р) {то виділимо для неї пам'ять)
{інакше виділити пам'ять неможливо)
else begin
writeln('Ділянки в ',sizeof(Т),' байт немає');
... {дії, пов'язані з відсутністю ділянки)
end;
При виконанні виклику dispose (р) ділянку пам'яті, на яку встановлено р, буде звільнено, але значення р не зміниться. Спроба використання звільненої ділянки пам'яті, зокрема, її звільнення, завершується аварійно, як, наприклад, виконання таких
new(р); ... dispose(р);
р^:=... {аварійне закінчення}
або таких операторів.
new(p); q:=p; ...
dispose(р);
dispose(q) {аварійне закінчення}
Розміри автоматичної та вільної пам'яті можна змінювати за допомогою підпункту ”Memory sizes” пункту «Options» меню системи Turbo Pascal, хоча розміри вільної пам'яті за узгодженням є максимально можливими.
Загальний розмір незайнятої частини купи повертається з виклику функції memavai1 (також без параметрів).