жна передавати значення тільки між покажчиками, пов'язаними з одним і тим же типом даних. Якщо, наприклад,
var
p1, p2: ^ integer;
р3: ^ real;
рр: pointer;
то присвоювання
р1: = р2;
цілком припустимо, в то час як
р1: = р3;
заборонено, оскільки Р1 і Р3 вказують на різні типи даних. Це обмеження, проте, не поширюється на нетипізовані покажчики, тому ми могли б записати
pp: = р3;
р1: = рр;
і тим самим досягти потрібного результату.
Читач має право задати питання, чи варто було вводити обмеження і тут же давати кошти для їх обходу. Вся справа в тому, що будь-яке обмеження, з одного боку, вводиться для підвищення надійності програм, а з іншого - зменшує потужність мови, робить його менш придатним для якихось застосувань. У Турбо Паскалі нечисленні виключення у відношенні типів даних надають мові необхідну гнучкість, але їх використання вимагає від програміста додаткових зусиль і таким чином свідчить про цілком усвідомленому дії.
Використання покажчиків
Підіб'ємо деякі підсумки. Отже, динамічна пам'ять складає 200 ... 300 Кбайт або більше, її початок зберігається в змінної HEAPORG, a кінець відповідає адресі змінної HEAPEND. Поточний адресу вільної ділянки динамічної пам'яті зберігається в покажчику HEAPPTR.
Подивимося, як можна використовувати динамічну пам'ять для розміщення великих масивів даних. Нехай, наприклад, потрібно забезпечити доступ до елементів прямокутної матриці 100х200 типу EXTENDED. Для размщеенія такого масиву вимагається пам'ять 200 000 байт (100 * 200 * 10). Здавалося б, цю проблему можна вирішити наступним чином:
var
i, j: integer;
PtrArr: array [1 .. 100, 1 .. 200] of ^ real;
begin
for i: = 1 to 100 do
for j: = 1 to 200 do
new (PtrArr [i, j]);
end.
Тепер до будь-якого елементу новоствореного динамічного масиву можна звернутися за адресою, наприклад:
PtrArr [1,1] ^ : = 0;
if PtrArr [i, j * 2] ^> 1 then
Згадаймо, однак, що довжина внутрішнього подання покажчика становить 4 байти, тому для розміщення масиву PTRARR буде потрібно 100 * 200 * 4 = 80000 байт, що перевищує розмір сегмента даних (65536 байт), доступний, як уже зазначалося, програмі для статичного розміщення даних. Виходом з положення могла б послужити адресна арифметика, тобто арифметика над покажчиками, тому що в цьому випадку можна було б відмовитися від створення масиву покажчиків PTRARR і обчислювати адресу будь-якого елементу прямокутної матриці безпосередньо перед зверненням до нього. Однак в Турбо Паскалі над покажчиками не визначені жодні операції, крім операцій привласнення і відносини.
Тим не менш, вирішити зазначену задачу таки можна. Як ми вже знаємо, будь покажчик складається з двох слів типу WORD, в яких зберігаються сегмент і зсув. У Турбо Паскалі визначені дві вбудовані функції типу WORD, що дозволяють одержати вміст цих слів:
SEG (X) - повертає сегментну частина адреси;
OFS (X) - повертає зсув.
Аргументом Х при зверненні до цих функцій може служити будь-яка змінна, в тому числі і та, на яку вказує покажчик. Наприклад, якщо маємо
var
р: ^ real;
begin
new (p);
p ^: = 3.14;
end
то функція SEG (P) поверне сегментну частина адреси, за якою розташовується 4-байтним покажчик Р, в той час як SEG (P ^) - сегмент 6-байтного ділянки купи, в якому зберігається число 3.14.
З іншого боку, з допомогою вбудованої функції PTR (SEG, OFS: WORD): POINTER можна створити значення покажчика, сумісний з покажчиками будь-якого типу. Таким чином, можлива така послідовність дій. Спочатку процедурою GETMEM з купи забираються кілька фрагментів відповідної довжини (нагадаю, що за одне звернення до процедурі можна зарезервувати не більше 65521 байт динамічної пам'яті). Для рас сматривать прикладу зручно резервувати фрагменти такої довжини щоб у них могли, наприклад, розміститися рядки прямокутної матриці, тобто
200 * 10 = 2000 байт.
Початок кожного фрагмента, тобто фактично початок розміщення в пам'яті кожного рядка, запам'ятовується в масиві PTRSTR, що складається з 100 вказівників. Тепер для доступу до будь елементу рядка потрібно обчислити зміщення цього елемента від початку рядка і сформувати відповідний покажчик:
var
i, j: integer;
PtrStr: array [1 .. 100] of pointer;
pr: ^ real;
const
SizeOfReal = 6;
begin
for i: = 1 to 100 do
GetMem (PtrStr [i], SizeOfReal * 200);
{Звернення до елементу матриці [i, j]}
pr: = ptr (seg (PtrStr [i] ^), ofs (PtrStr [i] ^) + (j-1) * SizeOfReal);
if pr ^> 1 then
end
Оскільки оператор обчислення адреси PR: = PTR ... буде, судячи з усього, використовуватися в програмі неодноразово, корисно ввести допоміжну функцію GETR, повертаючу значення елемента матриці, і процедуру PUTR, що встановлює нове значення елемента. Кожна з них, у свою чергу, звертається до функції ADDRR для обчислення адреси. Нижче наводиться програма, що створює в пам'яті матрицю з NxM випадкових чисел і обчислює їх середнє значення.
program Primer1;
const
SizeOfReal = 6; {Довжина змінної типу REAL}
N = 100; {Кількість стовпців}
М = 200; {Кількість рядків}
var
i, j: integer;
PtrStr: array [1 .. N] of pointer;
s: real;
type
RealPoint = ^ Real;
{}
Function AddrR (i, j: word): RealPoint;
{По сегменту i та зміщення j видає адреса речової змінної}
begin
AddrR: = ptr (seg (PtrStr [i] ^), ofs (PtrStr [i] ^) + (j-1) * SizeOfReal)
end; {AddrR}
{}
Function GetR (i, j: integer): real;
{Видає значення речової змінної по сегменту i
і зміщення j її адреси}
begin
GetR: = AddrR (i, j) ^
end; {GetR}
{}
Procepure PutR (i, j: integer; x: real);
{Поміщає в змінну, адреса якої має сегмент i
зсув j, речовий значення x}
begin
AddrR (i, j) ^: = x
end; {PutR}
{}
begin {Main}
for i: = 1 to N do
begin
GetMem (PtrStr [i], M * SizeOfReal);
for j: = 1 to M do PutR (i, j, Random)
end;
s: = 0;
for i: = 1 to N do
for j: = 1 to M do
s: = s + GetR (i, j);
WriteLn (s/(N * M): 12:10)
end. {Main}
У розглянутому прикладі передбачається, що кожен рядок розміщується в купі, починаючи з кордону параграфа, і зсув для кожного покажчика PTRSTR дорівнює нулю. В Насправді при послідовних зверненнях до процедури GETMEM початок чергового фрагмента слід відразу за кінцем попереднього і може не потрапити на кордон сегмента. В результаті, при розміщенні фрагментів максимальної довжини (65521 байт) може виникнути переповнення при обчисленні зміщення останнього байта.