Если вы программировали на С/С++, то, возможно, вы знакомы с таким понятием как указатели. Указатели позволяют получить доступ к определенной ячейке памяти и произвести определенные манипуляции со значением, хранящимся в этой ячейке.
В языке C# указатели редко используются, однако в некоторых случаях можно прибегать к ним для оптимизации приложений. Код, применяющий указатели, еще называют небезопасным (unsafe) кодом. Однако это не значит, что он представляет какую-то опасность. Просто при работе с ним все действия по использованию памяти, в том числе по ее очистке, ложится целиком на нас, а не на среду CLR. И с точки зрения CLR такой код не безопасен, так как среда не может проверить данный код, поэтому повышается вероятность различного рода ошибок.
Чтобы использовать небезопасный код в C#, надо первым делом указать проекту, что он будет работать с небезопасным кодом. Для этого надо установить в настройках проекта соответствующий флаг - в меню Project (Проект) найти Свойства проекта. Затем в меню Build установить флажок Unsafe code (Небезопасный код):
Теперь мы можем приступать к работе с небезопасным кодом и указателями.
Блок кода или метод, в котором используются указатели, помечается ключевым словом unsafe:
// блок кода, использующий указатели unsafe { }
Метод, использующий указатели:
unsafe void Test() { }
Также с помощью unsafe можно объявлять структуры и классы:
unsafe struct State { } unsafe class Person { }
Ключевой при работе с указателями является операция *
, которую еще называют операцией разыменовывания. Операция разыменовывания
позволяет получить или установить значение по адресу, на который указывает указатель. Для получения адреса переменной
применяется операция &
:
unsafe { int* x; // определение указателя int y = 10; // определяем переменную x = &y; // указатель x теперь указывает на адрес переменной y Console.WriteLine(*x); // 10 y = y + 20; // меняем значение Console.WriteLine(*x);// 30 *x = 50; Console.WriteLine(y); // переменная y=50 }
При объявлении указателя указываем тип int* x;
- в данном случае объявляется указатель на целое число. Но кроме типа
int
можно использовать и другие: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal или bool
.
Также можно объявлять указатели на типы enum
, структуры и другие указатели.
Выражение x = &y;
позволяет нам получить адрес переменной y и установить на него указатель x. До этого указатель x не на что не указывал.
После этого все операции с y будут влиять на значение, получаемое через указатель x и наоборот, так как они указывают на одну и ту же область в памяти.
Для получения значения, которое хранится в области памяти, на которую указывает указатель x, используется выражение *x
.
Используя преобразование указателя к целочисленному типу, можно получить адрес памяти, на который указывает указатель:
int* x; // определение указателя int y = 10; // определяем переменную x = &y; // указатель x теперь указывает на адрес переменной y // получим адрес переменной y ulong addr = (ulong)x; Console.WriteLine($"Адрес переменной y: {addr}");
Для получения адреса используется преобразование в тип uint, long или ulong. Так как значение адреса - это целое число, а на 32-разрядных системах диапазон адресов 0 до 4 000 000 000, а адрес можно получить в переменную uint/int. Соответственно на 64-разрядных системах диапазон доступных адресов гораздо больше, поэтому в данном случае лучше использовать ulong, чтобы избежать ошибки переполнения.
Объявление и использование указателя на указатель:
unsafe { int* x; // определение указателя int y = 10; // определяем переменную x = &y; // указатель x теперь указывает на адрес переменной y int** z = &x; // указатель z теперь указывает на адрес, указателя x **z = **z + 40; // изменение указателя z повлечет изменение переменной y Console.WriteLine(y); // переменная y=50 Console.WriteLine(**z); // переменная **z=50 }