// Доступ к членам через указатель.

  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());

}

<p id="AutBody_Root440">Ключевое слово stackalloc</p>

В небезопасном контексте может возникнуть необходимость в объявлении локальной переменной, для которой память выделяется непосредственно в стеке вызовов (и потому она не обрабатывается сборщиком мусора .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);

<p id="AutBody_Root441">Закрепление типа посредством ключевого слова fixed</p>

В предыдущем примере вы видели, что выделение фрагмента памяти внутри небезопасного контекста может делаться с помощью ключевого слова 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 позволяет строить оператор, который фиксирует ссылочную переменную в памяти, чтобы ее адрес оставался постоянным на протяжении работы оператора (или блока операторов). Каждый раз, когда вы взаимодействуете со ссылочным типом из контекста небезопасного кода, закрепление ссылки обязательно. 

<p id="AutBody_Root442">Ключевое слово sizeof</p>
Перейти на страницу:

Похожие книги