// Доступ к членам через указатель.
Point;
Point* p = &point
p->x = 100;
p->y = 200;
Console.WriteLine(p->ToString());
// Доступ к членам через разыменованный указатель.
Point point2;
Point* p2 = &point2
(*p2).x = 100;
(*p2).y = 200;
Console.WriteLine((*p2).ToString());
}
Ключевое слово stackalloc
В небезопасном контексте может возникнуть необходимость в объявлении локальной переменной, для которой память выделяется непосредственно в стеке вызовов (и потому она не обрабатывается сборщиком мусора .NET Core). Для этого в языке C# предусмотрено ключевое слово stackalloc, которое является эквивалентом функции _аllоса библиотеки времени выполнения С. Вот простой пример:
static unsafe string UnsafeStackAlloc()
{
char* p = stackalloc char[52];
for (int k = 0; k < 52; k++)
{
p[k] = (char)(k + 65)k;
}
return new string(p);
}
Закрепление типа посредством ключевого слова fixed
В предыдущем примере вы видели, что выделение фрагмента памяти внутри небезопасного контекста может делаться с помощью ключевого слова stackalloc. Из-за природы операции stackalloc выделенная память очищается, как только выделяющий ее метод возвращает управление (т.к. память распределена в стеке). Однако рассмотрим более сложный пример. Во время исследования операции -> создавался тип значения по имени Point. Как и все типы значений, выделяемая его экземплярам память исчезает из стека по окончании выполнения. Предположим, что тип Point взамен определен как
class PointRef // <= Renamed and retyped.
{
public int x;
public int y;
public override string ToString() => $"({x}, {y})";
}
Как вам известно, если в вызывающем коде объявляется переменная типа Point, то память для нее выделяется в куче, поддерживающей сборку мусора. И тут возникает животрепещущий вопрос: а что если небезопасный контекст пожелает взаимодействовать с этим объектом (или любым другим объектом из кучи)? Учитывая, что сборка мусора может произойти в любое время, вы только вообразите, какие проблемы возникнут при обращении к членам Point именно в тот момент, когда происходит реорганизация кучи! Теоретически может случиться так, что небезопасный контекст попытается взаимодействовать с членом, который больше не доступен или был перемещен в другое место кучи после ее очистки с учетом поколений (что является очевидной проблемой).
Для фиксации переменной ссылочного типа в памяти из небезопасного контекста язык C# предлагает ключевое слово fixed. Оператор fixed устанавливает указатель на управляемый тип и "закрепляет" такую переменную на время выполнения кода. Без fixed от указателей на управляемые переменные было бы мало толку, поскольку сборщик мусора может перемещать переменные в памяти непредсказуемым образом. (На самом деле компилятор C# даже не позволит установить указатель на управляемую переменную, если оператор fixed отсутствует.)
Таким образом, если вы создали объект Point и хотите взаимодействовать с его членами, тогда должны написать следующий код (либо иначе получить ошибку на этапе компиляции):
unsafe static void UseAndPinPoint()
{
PointRef pt = new PointRef
{
x = 5,
y = 6
};
// Закрепить указатель pt на месте, чтобы он не мог
// быть перемещен или уничтожен сборщиком мусора.
fixed (int* p = &pt.x)
{
// Использовать здесь переменную int*!
}
// Указатель pt теперь не закреплен и готов
// к сборке мусора после завершения метода.
Console.WriteLine ("Point is: {0}", pt);
}
Выражаясь кратко, ключевое слово fixed позволяет строить оператор, который фиксирует ссылочную переменную в памяти, чтобы ее адрес оставался постоянным на протяжении работы оператора (или блока операторов). Каждый раз, когда вы взаимодействуете со ссылочным типом из контекста небезопасного кода, закрепление ссылки обязательно.
Ключевое слово sizeof