Любопытны три последних строки. Они показывают, что размеры указателей на переменные всех типов одинаковы и для 32-разрядных систем составляют 4 байта (подобно тому, как размер конверта не зависит от размера дома, куда он адресован).
В следующей главе мы пожнем первые плоды от применения указателей, а пока подведем итоги.
• Память компьютера – это последовательность ячеек, которым назначены уникальные адреса.
• Объекты программы – переменные, процедуры и функции – занимают ячейки памяти, адреса которых можно определить операцией взятия адреса @ или функцией Addr.
• Для хранения адресов применяют переменные особого типа – указатели. Каждому типу переменных соответствует свой тип указателя.
• Перед использованием указателя ему присваивают либо адрес переменной, либо пустое значение NIL.
• С указателями допустимы лишь три операции: копирование, сравнение и разыменование.
• Разыменованный указатель – это переменная, на которую он ссылается в данный момент; с ним можно поступать как с этой переменной.
• Указатели всех типов имеют одинаковый размер, который для 32-разрядных операционных систем составляет 4 байта.
А) Какие ошибки найдет компилятор в следующей программе? Объясните их.
var P1 : ^Integer; P2 : ^String;
N : Integer; S : String;
begin
P1 := @S;
P2 := @N;
end.
Б) Будет ли работать следующая программа? В чём ошибки?
var P1 : ^Integer;
begin
P1 := 0;
P1^ := 30;
P1 := nil; Writeln(P1^);
end.
В) Откройте программу «P_51_1» и введите в окно обзора переменные P1 и P1^ (комбинацией Ctrl+F7). Выполняя программу по шагам, наблюдайте за переменными. Сделайте то же с программой «P_51_2».
Глава 52
Динамические переменные
В предыдущей главе вы узнали о размещении данных в оперативной памяти и познакомились с указателями, хранящими адреса переменных. Это была присказка… Ведь самый жгучий вопрос остался без ответа – зачем нужны эти указатели?
В программах для сортировки таблицы чемпионата и обработки классного журнала был заранее известен объёмом данных. Действительно, количество клубов в чемпионате известно любому болельщику. Чуть сложнее с классным журналом, – ведь ученики приходят и уходят. Но, взяв размер массива учеников с некоторым разумным запасом, мы решаем и эту проблему.
Но так будет не всегда. Есть немало задач, где предугадать объём данных нельзя даже приблизительно. Вот, к примеру, полицейская база данных по угнанным автомобилям, каков должен быть её размер? Тысяча или миллион элементов? В спокойной стране достаточно будет десятка, а там, где угоняют каждый третий автомобиль… Ох! Лучше не спрашивайте! Можно, конечно, объявить массив с солидным запасом, но это породит ещё две проблемы. Во-первых, большая часть массива вероятней всего будет пустовать, – разумно ли транжирить память попусту? Второй случай ещё злее: в какой-то момент не хватит даже этого запаса, и программа «рухнет».
Безупречное решение – выделять данным ровно столько памяти, сколько нужно. То есть, создавать переменные, когда нам надо, и уничтожать их, когда потребность в них отпадает. Отличная мысль! Двинемся в этом направлении!
Вернитесь к рисунку 51-й главы, где показано размещение программ в оперативной памяти. Большая часть этой памяти остается «не вспаханной», свободной. Куча – так принято её называть (по-английски – Heap). Операционная система распоряжается кучей по своему усмотрению, и все же большие куски этой памяти простаивают без дела. Нельзя ли программе временно одолжить частичку? Оказывается, можно! Надо лишь освоить работу с указателями. В предыдущей главе мы применяли указатели на переменные, не видя в том особой пользы. Другое дело – участки памяти в куче, у которых нет имени. Здесь указатели – единственное средство для доступа к этим залежам.
Поскольку кучей заведует операционная система, за памятью обращаются к ней. Для этого в Паскале предусмотрено несколько процедур и функций, две из которых – процедуры New и Dispose – мы и рассмотрим.
Для получения кусочка памяти из кучи, вызывают процедуру New (что значит «новый»). Этой процедуре нужен лишь один параметр – указатель некоторого типа. Процедура бронирует в куче кусок соответствующего размера, и адрес этого куска помещает в указатель. Так рождается новая переменная в куче. Рассмотрим пример.
var York : ^Integer; { указатель на целое }
begin
New(York); { выделено два байта из кучи, адрес в переменной York }
York^:=123;
Writeln(York^); { 123 }
end.