Размерные и ссылочные типы

Большинство типов в PascalABC.NET подразделяются на две большие группы: размерные и ссылочные.

К размерным относятся все простые типы и записи. Более точно, все размерные типы наследуются от .NET-типа System.ValueType.

К ссылочным типам относятся строки, динамические массивы, кортежи, классы, последовательности и процедурный тип. Более точно, все ссылочные типы наследуются от .NET-типа System.Object и не наследуются от System.ValueType.

Размерные и ссылочные типы отличаются следующими характеристиками:

Кроме того, в PascalABC.NET имеется несколько типов, унаследованных от Delphi Object Pascal, которые трудно отнести к размерному или ссылочному типу. Это статические массивы, множества, размерные строки и файлы. По представлению в памяти они относятся к ссылочному типу, но по поведению (присваивание, сравнение, передача в подпрограммы) - к размерному.

Размерные типы более эффективны при вычислениях: они занимают меньше памяти и операции, выполняемые над небольшими размерными типами, максимально эффективны. Ссылочные типы обладают большей гибкостью: память под них выделяется динамически в процессе работы программы и освобождается автоматически когда объект ссылочного типа перестаёт использоваться.

Выделение памяти

Память под переменную размерного типа выделяется на программном стеке. Для глобальных переменных память выделяется при запуске программы, для локальных - в момент вызова подпрограммы. При этом переменная размерного типа хранит значение этого типа.

var i: integer; // под i выделяется память на программном стеке
i := 5;

Переменная ссылочного типа хранит ссылку на объект некоторого класса в динамической памяти. Если она не инициализирована, то хранит специальное значение nil (нулевая ссылка). Для инициализации ссылочных переменных используется вызов конструктора соответствующего класса:

type Person = auto class
  name: string;
  age: integer;
end;

var p: Person; // p хранит значение nil, память под объект не выделена
p := new Person('Иванов',20); // конструктор выделяет память под объект и записывает ссылку на него в переменную p
 

Особенности PascalABC.NET

Строки в PascalABC.NET инициализируются по умолчанию не значением nil, а пустой строкой.

Присваивание

При присваивании переменных размерного типа копируется значение этого типа.

type Point3 = record
  x,y,z: real;
end;

var p1,p2: Point3;
p1.x := 1; p1.y := 2; p1.z := 3;
p2 := p1;  // копируются все поля
Print(p2); // (1,2,3)
p1.x := 4; p1.y := 5; p1.z := 6;
Print(p2); // (1,2,3) - p2 не меняется, т.к. занимает на стеке другую память

При присваивании переменных ссылочного типа копируется ссылка, в итоге после присваивания обе ссылки ссылаются на один объект в динамической памяти:

type Point3 = auto class
  x,y,z: real;
end;

var p1,p2: Point3; // переменные хранят нулевую ссылку nil
p1 := new Point3(1,2,3);
p2 := p1;  // копируется ссылка, после чего p2 ссылается на тот же объект, что и p1
Print(p2); // (1,2,3)
p1.x := 4; p1.y := 5; p1.z := 6;
Print(p2); // (4,5,6) - объект поменялся, p2 ссылается на тот же объект, что и p1

Статические массивы, множества и размерные строки при присваивании ведут себя как размерные типы. Так, при присваивании одного статического массива другому копируются все элементы:

var a,a1: array [1..1000000] of integer;
a1 := a; // копируются все 1000000 элементов (долго)

Сравнение на равенство

Сравнение на равенство и на неравенство объектов размерного типа сравнивает их значения. В частности, две переменные типа запись равны если равны все поля этих записей.

type PersonRec = record
  name: string;
  age: integer;
end;
var
p,p1: PersonRec;
p.name := 'Иванов'; p.age := 20;
p1.name := 'Иванов'; p1.age := 20;
writeln(p1 = p); // True

Две переменные ссылочного типа по умолчанию равны если они ссылаются на один и тот же объект.

type Person = auto class
  name: string;
  age: integer;
end;
var
p := new Person('Иванов',20);
var p1 := new Person('Иванов',20);
writeln(p1 = p); // False
 

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

Передача в подпрограммы

При передаче размерных типов по значению происходит копирование значения фактического параметра в переменную-формальный параметр. Если размерный тип имеет большой размер, это может занимать продолжительное время, поэтому размерный тип в этом случае передаётся по ссылке на константу:

type Point3 = record
  x,y,z: real;
end;

procedure PrintPoint(const p: Point3);
begin
  Print(p.x,p.y,p.z)
end;

Ссылочные типы передаются в подпрограмму, как правило, по значению. При передаче таких параметров происходит копирование ссылки, в результате формальный и фактический параметр будут ссылаться на один объект.

procedure Change666(a: array of integer);
begin
  a[0] := 666;
end;

При этом в результате изменения формального параметра внутри подпрограммы меняется и содержимое соответствующего фактического параметра при вызове подпрограммы.

Ссылочные типы передаются в подпрограмму по ссылке только в случае если сама ссылка меняется внутри подпрограммы:

procedure CreateA(var a: array of integer);
begin
  a := new integer[10];
end;

Статические массивы, размерные строки и множества при передаче в подпрограммы ведут себя как размерные типы. Например, неэффективно пытаться передать в подпрограмму статический массив по значению, поскольку происходит копирование большого объёма данных. Поэтому статические массивы всегда передаются по ссылке:

type Arr = array [1..100] of integer;

procedure PrintArray(const a: Arr; n: integer);
begin
  for var i:=1 to n do
    Print(a[i])
end;

Управление памятью

Размерные типы распределяются на программном стеке, поэтому не нуждаются в специальном управлении памятью. Под глобальные размерные переменные память распределена всё время работы программы. Под локальные размерные переменные память выделяется в момент вызова подпрограммы, а освобождается в момент завершения работы этой подпрограммы.

Управление памятью для ссылочных типов осуществляется автоматически сборщиком мусора. Сборщик мусора запускается в неопределенный момент времени когда управляемой памяти перестаёт хватать. Он возвращает в пул неиспользуемой памяти те объекты, на которые больше никто не ссылается, после чего дефрагментирует оставшуюся память, в результате чего динамическая память всегда дефрагментирована и ее выделение при вызове конструктора происходит практически мгновенно.

Статические массивы, размерные строки, множества и файлы с точки зрения распределения памяти относятся к ссылочным и память, занимаемая значениями этого типа, также управляется сборщиком мусора.