Расположение информации в оперативной памяти. Адреса

Этот и следующий параграфы носят ознакомительный характер.

Раньше я уподоблял оперативную память тетрадному листу в клеточку. Каждая клетка - байт. Теперь я уподоблю ее многоэтажному небоскребу. Каждый этаж - байт.

Как и положено этажам, байты имеют номера. Эти номера называются адресами. Самый "нижний" байт имеет адрес 0, следующий - 1, следующий - - 2 и т.д. Если память вашего компьютера имеет объем 1 Мегабайт, то вы сами можете вычислить адрес последнего байта, учитывая, что 1 Мегабайт = 1024 Килобайта, a 1Килобайт = 1024 байта. Приняты сокращения: Мегабайт - М, Килобайт - К. Имейте в виду, что во многих книгах адреса записываются не в привычном нам виде, а в так называемой шестнадцатеричной системе счисления.

Во время выполнения вашей программы, написанной на Паскале, в памяти находится самая разная информация. То, что относится к паскалевской программе, располагается "по этажам" в следующем порядке:

байт с адресом 1М-1  
    куча    
   
    стек
    сегмент данных объемом 64К
      откомпилированная программа
  байт с адресом 0  

Границы между некоторыми областями памяти не фиксированы и зависят от решаемой задачи и желания программиста. В сегменте данных располагаются переменные, массивы и другие типы данных вашей программы, описанные привычным вам способом в разделах VAR, CONST и т.д. (без использования ссылок). Обратите внимание, что размер сегмента данных весьма невелик (не более 64К). Стек- область памяти, в которой располагаются данные, описанные внутри процедур (этого мы пока не делали, об этом - в Глава 13). Куча- область памяти, в которой располагаются данные, описанные при помощи ссылок.

Ссылки

Пусть вы хотите использовать следующий массив:

VAR a: array[1..200, 1..200] of Integer;

Давайте подсчитаем, сколько байтов в памяти займет этот массив. Одно число типа Integer занимает 2 байта. Получаем 200*200*2 = 80000 байтов. В сегменте данных массив не умещается, значит привычным образом работать с ним нельзя. Использование ссылок позволяет разместить его в куче(по английски - heap), имеющей гораздо больший размер.

Я привел лишь один из доводов в пользу применения ссылок. А поближе познакомимся мы со ссылками на простом примере. Задача: Вычислить и напечатать y=a+b, где a и b - целые числа 2 и 3. Вот традиционная программа для решения этой задачи:

VAR a, b, y : Integer;
BEGIN
a:=2; b:=3;
y:=a+b;
WriteLn (y)
END.

А теперь потребуем, чтобы число 2 и результат 5 размещались в куче (впрочем, строго говоря, не обязательно в куче). Вот программа со ссылками:

VAR b : Integer;
a,y : ^Integer;
BEGIN
New(a); New(y);
a^ := 2; b:=3;
y^ := a^ + b;
WriteLn (y^)
END.

Пояснения: Все, что выше BEGIN, выполняется на этапе компиляции: Строка a,y:^Integer приказывает отвести в памяти в сегменте данных две ячейки, но не для будущих чисел 2 и 5, а для адресов ячеек из кучи, в которых эти самые 2 и 5 предполагается хранить. Итак, будущие значения a и y - не числа 2 и 5, а адресаячеек для этих чисел или, по-другому, ссылкина ячейки для этих чисел. Пока же адреса эти не определены.

Все, что ниже BEGIN, выполняется на этапе выполнения программы: При помощи обращений к процедуре New ( New(a) и New(y) ) мы идем дальше и придаем переменным a и y значения конкретных адресов памяти, то есть отводим для будущих чисел 2 и 5 конкретное место в памяти. Таким образом, мы сталкиваемся с новым для нас явлением - место в памяти отводится не на этапе компиляции, а на этапе выполнения программы. В Паскале имеются средства и освобождать это место на этапе выполнения программы (процедура Dispose, на которой я не буду останавливаться). Называется все это динамическим распределением памятии сулит выгоды экономным создателям программ, использующим большие объемы разных данных в разные моменты выполнения программы.

Оператор a^:= 2 идет еще дальше и посылает в ячейку, адрес которой находится в ячейке a, число 2. Обозначается такая ячейка - a^. Если бы мне вздумалось написать a:=2, это бы значило, что я хочу послать в ячейку a адрес равный двум, что вполне возможно, но синтаксически неверно, так как численные значения адресов задаются по-другому.

Смысл следующих двух операторов очевиден.

Подведем итог. Значок ^, поставленный перед типом (например, ^Integer), означает новый ссылочный тип, значения которого обязаны быть адресами переменной (или ссылками на переменную) исходного типа (в нашем случае Integer).

Значок ^, поставленный после переменной ссылочного типа (например, a^), означает переменную, на которую ссылается исходная переменная (в нашем случае исходная переменная a).

Вот еще некоторые возможные операции со ссылками (без особых пояснений):

TYPE D = array[1..10] of Real;
DP = ^D;
Int = ^Integer;
VAR i, j : Int; { i, j - адреса целых чисел}
m : DP; { m - адрес первой ячейки массива из 10 вещ. чисел}
BEGIN
New(i); New(j); New(m);
i^:=4;
j^:=3;
j:=i; {Теперь j и i содержат адреса одного и того же числа - 4}
WriteLn(j^); {поэтому будет напечатано число 4}
m^[9]:=300 {Девятый элемент массива становится равным числу 300}
END.

Вернемся к задаче о размещении большого массива. Поскольку Паскаль вслед за MS-DOS запрещает не только описывать, но также, естественно, и ссылаться на структуру, объемом превышающую 64К, то ссылаться сразу на весь двумерный массив не выйдет и поэтому программа получится непростой:

TYPE a = array[1..200] of Integer;
ap = ^a;
a2 = array[1..200] of ap;
VAR x : a2; {x - массив из 200 адресов (каждый - ссылка на строку из 200 элементов

исходного массива)}


BEGIN
for i:=1 to 200 do New(x[i]); {Место для массива отведено}
............
x[128]^[35]:=800; {Присвоено значение элементу массива}
.............
END.

В заключение упомяну, что ссылки полезны еще тем, что позволяют организовывать в памяти структуры переменной длины, такие как списки, деревья и т.п.

Наши рекомендации