Выбрать главу

Указатель    Переменная

адрес -► значение

Одноуровневая непрямая адресация

Указатель    Указатель    Переменная

адрес -► адрес -► значение

Многоуровневая непрямая адресация Рис. 20.1. Одно- и многоуровневая непрямая адресация

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

Переменная, являющаяся указателем на указатель, должна быть объявлена как таковая. Для этого достаточно указать дополнительный знак * после имени типа переменной. Например, в следующем объявлении компилятор уведомляется о том, что переменная q является указателем на указатель и относится к типу int.

int** q;

Следует, однако, иметь в виду, что переменная q является указателем не на целое значение, а на указатель типа int.

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

using System;

class Multiplelndirect {

unsafe static void Main() {

int x; // содержит значение типа int

int* p; // содержит указатель типа int

int** q; // содержит указатель на указатель типа int

х = 10;

р = &х; // поместить адрес переменной х в переменной р q = &р; // поместить адрес переменной р в переменной q

Console.WriteLine(**q); // вывести значение переменной х

}

}

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

И последнее замечание: не путайте многоуровневую непрямую адресацию со структурами данных высокого уровня, в том числе связными списками, так как это совершенно разные понятия.

Массивы указателей

Указатели могут быть организованы в массивы, как и любой другой тип данных. Ниже приведен пример объявления массива указателей типа int длиной в три элемента.

int * [] ptrs = new int * [3];

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

ptrs[2] = &var;

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

*ptrs[2]

Оператор sizeof

Во время работы с небезопасным кодом иногда полезно знать размер в байтах одного из встроенных в C# типов значений. Для получения этой информации служит оператор sizeof. Ниже приведена его общая форма:

sizeof(тип)

где тип обозначает тот тип, размер которого требуется получить. Вообще говоря, оператор sizeof предназначен главным образом для особых случаев и, в частности, для работы со смешанным кодом: управляемым и неуправляемым.

Оператор stackalloc

Для распределения памяти, выделенной под стек, служит оператор stackalloc. Им можно пользоваться лишь при инициализации локальных переменных. Ниже приведена общая форма этого оператора: тип *р = stackalloc тип [размер]

где р обозначает указатель, получающий адрес области памяти, достаточной для хранения объектов, имеющих указанный тип, в количестве, которое обозначает размер. Если же в стеке недостаточно места для распределения памяти, то генерируется исключение System. StackOverflowException. И наконец, оператор stackalloc можно использовать только в небезопасном коде.

Как правило, память для объектов выделяется из кучи — динамически распределяемой свободной области памяти. А выделение памяти из стека является исключением. Ведь переменные, располагаемые в стеке, не удаляются средствами "сборки мусора", а существуют только в течение времени выполнения метода, в котором они объявляются. После возврата из метода выделенная память освобождается. Преимущество применения оператора stackalloc заключается, в частности, в том, что в этом случае не нужно беспокоиться об очистке памяти средствами "сборки мусора".