Содержание
C# поддерживает прямое управление памятью с помощью указателей в пределах блоков кода помеченных как небезопасный код и при условии компиляции с параметром /unsafe
. Указатели могут быть полезны при работе с API написанными на C, а также для доступа к памяти вне управляемого хипа (heap) или особо критичных в плане производительности местах.
Основы указателей (Pointer)
Для каждого значимого или ссылочного типа V
существует соответствующий указательный тип (pointer type) V*
. Экземпляр указателя хранит адрес переменной. Указательный тип может быть приведен к любому другому указательному типу. Основными операторами для указателей являются:
&
— оператор взятия адреса (address-of) — возвращает указатель на адрес переменной*
— оператор разыменования (dereference) — возвращает переменную по адресу указателя->
— оператор указатель на член (pointer-to-member) — используется как сокращенный синтаксис:x->y
эквивалент(*x).y
Небезопасный код
Если отметить тип, член типа или блок инструкций ключевым словом unsafe, то внутри их области видимости можно использовать указательные типы и выполнять операции в стиле указателей C++ для доступа к памяти:
1 2 3 4 5 6 7 8 9 10 | unsafe void BlueFilter (int[,] bitmap) { int length = bitmap.Length; fixed (int* b = bitmap) { int* p = b; for (int i = 0; i < length; i++) *p++ &= 0xFF; } } |
Небезопасный код может выполняться быстрее чем соответствующая безопасная реализация.
Инструкция fixed
Инструкция fixed
используется для того, чтобы закрепить управляемый объект, такой как bitmap
в предыдущем примере. Во время выполнения программы многие объекты добавляются и удаляются из хипа. Чтобы избежать случайной потери или фрагментации памяти сборщик мусора передвигает объекты. Указатель на объект станет неверным если его адрес измениться пока указатель на него ссылается. Поэтому инструкция fixed
заставляет сборщик мусора закрепить объект и не передвигать его. Это может отрицательно сказаться на производительности программы во время выполнения, поэтому зафиксированные блоки должны использоваться быстро.
В пределах инструкции fixed
указатель можно установить на значимый тип, на массив значимых типов или на строку. В случае массива или строки указатель в действительности будет указывать на их первый элемент, являющийся значимым типом.
Чтобы закрепить значимый тип, объявленный внутри ссылочного, необходимо закрепить этот ссылочный тип:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Test { int x; unsafe static void Main() { Test test = new Test(); fixed (int* p = &test.x) { *p = 9; } System.Console.WriteLine (test.x); } } |
Оператор-указатель на член
В дополнение к операторам &
и *
C# позволяет использовать оператор ->
в стиле C++. Он может быть использован для структур:
1 2 3 4 5 6 7 8 9 10 11 | struct Test { int x; unsafe static void Main() { Test test = new Test(); Test* p = &test; p->x = 9; System.Console.WriteLine (test.x); } } |
Ключевое слово stackalloc
Участок памяти, на который ссылается указатель, может быть размещен в стэке в виде массива с помощью ключевого слова stackalloc
. В этом случае время его жизни будет ограничено временем выполнения метода, как для всех локальных переменных:
1 2 3 | int* a = stackalloc int [10]; for (int i = 0; i < 10; ++i) Console.WriteLine (a[i]); |
void*
Пустой указатель — void*
— не устанавливает соглашений о типе данных, на которые ссылается указатель, поэтому он может быть использован для взаимодействия с необработанной памятью. Любой указательный тип может быть автоматически преобразован в void*
. void*
не может быть разыменован (т.е. нельзя получить значение переменной по указателю), а также для него не доступны арифметические операции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | unsafe static void Main() { short[] a = {1,1,2,3,5,8,13,21,34,55}; fixed (short* p = a) { Zap (p, a.Length * sizeof (short)); } foreach (short x in a) System.Console.WriteLine (x); } unsafe static void Zap (void* memory, int byteCount) { byte* b = (byte*) memory; for (int i = 0; i < byteCount; i++) *b++ = 0; } |