Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

// Нет! В C# это некорректно!

int *pi, *pj;

// Да! Так поступают в С#.

int* pi, pj;

Рассмотрим следующий небезопасный метод:

static unsafe void PrintValueAndAddress

{

int myInt;

// Определить указатель на int и присвоить ему адрес myInt.

int* ptrToMyInt = &myInt;

//
Присвоить значение myInt, используя обращение через указатель.

*ptrToMyInt = 123;

// Вывести некоторые значения.

Console.WriteLine("Value of myInt {0}", myInt);

// значение myInt

Console.WriteLine("Address of myInt {0:X}", (int)&ptrToMyInt);

// адрес myInt

}

В результате запуска этого метода из блока

unsafe
вы получите такой вывод:

**** Print Value And Address ****

Value of myInt 123

Address of myInt 90F7E698

Небезопасная (и безопасная) функция обмена

Разумеется, объявлять указатели на локальные переменные, чтобы просто присваивать им значения (как в предыдущем примере), никогда не понадобится и к тому же неудобно. В качестве более практичного примера небезопасного кода предположим, что необходимо построить функцию обмена с использованием арифметики указателей:

unsafe static void UnsafeSwap(int* i, int* j)

{

int temp = *i;

*i = *j;

*j = temp;

}

Очень похоже на язык С, не так ли? Тем не менее, учитывая предшествующую работу, вы должны знать, что можно было бы написать безопасную версию алгоритма обмена с применением ключевого слова

ref
языка С#:

static void SafeSwap(ref int i, ref int j)

{

int temp = i;

i = j;

j = temp;

}

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

Console.WriteLine("***** Calling method with unsafe code *****");

// Значения, подлежащие обмену.

int i = 10, j = 20;

// "Безопасный" обмен значений местами.

Console.WriteLine("\n***** Safe swap *****");

Console.WriteLine("Values before safe swap: i = {0}, j = {1}", i, j);

SafeSwap(ref i, ref j);

Console.WriteLine("Values after safe swap: i = {0}, j = {1}", i, j);

// "Небезопасный" обмен значений местами.

Console.WriteLine("\n***** Unsafe swap *****");

Console.WriteLine("Values before unsafe swap: i = {0}, j = {1}", i, j);

unsafe { UnsafeSwap(&i, &j); }

Console.WriteLine("Values after unsafe swap: i = {0}, j = {1}", i, j);

Console.ReadLine;

Доступ

к полям через указатели (операция ->)

Теперь предположим, что определена простая безопасная структура

Point
:

struct Point

{

public int x;

public int y;

public override string ToString => $"({x}, {y})";

}

В случае объявления указателя на тип

Point
для доступа к открытым членам структуры понадобится применять операцию доступа к полям (имеющую вид
– >
). Как упоминалось в табл. 11.2, она представляет собой небезопасную версию стандартной (безопасной) операции точки (
.
). В сущности, используя операцию обращения к указателю (
*
), можно разыменовывать указатель для применения операции точки. Взгляните на следующий небезопасный метод:

static unsafe void UsePointerToPoint

{

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

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
взамен определен как ссылочный:

Поделиться с друзьями: