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

ЖАНРЫ

ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание

Троелсен Эндрю

Шрифт:

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

int *pi, *pj;

// Да! Это в C# правильно.

int* pi, pj;

Рассмотрим следующий пример.

unsafe {

 int myInt;

 // Определения указателя типа int

 // и присваивание ему адреса myInt.

 int* ptrToMyInt = &myInt;

 // Присваивание значения myInt

 // с помощью
разыменования указателя
.

 *ptrToMyInt = 123;

 // Печать статистики.

 Console.WriteLine("Значение myInt {0}", myInt);

 Console.WriteLine("Адрес myInt {0:X}", (int)&ptrToMyInt);

}

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

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

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

 int temp = *i;

 *i = *j;

 *j = temp;

}

Очень похоже на C, не так ли? Однако с учетом знаний, полученных из главы 3, вы должны знать, что можно записать следующую безопасную версию алгоритма обмена, используя ключевое слово C# ref.

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

 int temp = i;

 i = j;

 j = temp;

}

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

static void Main(string[] args) {

 Console.WriteLine(*** Вызов метода с небезопасным кодом ***");

 // Значения для обмена.

 int i = 10, i = 20;

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

 Console.WriteLine("\n***** Безопасный обмен *****");

 Cоnsоle.WriteLine("Значения до обмена: i = {0}, j = {1}", i, j);

 SafeSwap(ref 1, ref j);

 Console.WriteLine("Значения после обмена: i = {0}, j = {l}", i, j);

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

 Console.WriteLine("\n***** Небезопасный обмен *****");

 Console.WriteLine("Значения до обмена: i = {0}, j = {1}", i, j);

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

 Console.WriteLine("Значения после обмена: i = {0}, j = {1}", i, j);

 Console.ReadLine;

}

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

Теперь предположим, что у нас определена структура Point и мы хотим объявить указатель на тип Point. Как и в C(++), для вызова методов или получения доступа к полям типа указателя необходимо использовать операцию доступа к полю указателя (-›). Как уже упоминалось в табл. 9.3, это небезопасная версия стандартной (безопасной) операции, обозначаемой точкой (.). Фактически, используя операцию разыменования указателя (*). можно снять косвенность указателя, чтобы (снова) вернуться к применению

нотации, обозначаемой точкой. Рассмотрите следующий программный код.

struct Point {

 public int x;

 public int y;

 public override string ToString { return string.Format ("({0}, {1})", x, y); }

}

static void Main(string[] args) {

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

 unsafe {

Point point;

Point* p =&point;

p-›x = 100;

p-›y = 200;

Console.WriteLine(p-›ToString);

 }

 // Доступ к членам через разыменование указателей.

 unsafe {

Point point;

Point* p =&point;

(*p).x = 100;

(*p).y = 200;

Console.WriteLine((*p).ToString);

 }

}

Ключевое слово stackalloc

В небезопасном контексте может понадобиться объявление локальной переменной, размещаемой непосредственно в памяти стека вызовов (и таким образом не подлежащей "утилизации" при сборке мусора .NET). Чтобы сделать такое объявление, в C# предлагается ключевое слово stackalloc являющееся C#-эквивалентом функции alloca из библиотеки времени выполнения C. Вот простой пример.

unsafe {

 char* p = stackalloc char[256];

 for (int k = 0; k ‹ 256; k++) p[k] = (char)k;

}

Фиксация типа с помощью ключевого слова fixed

Как показывает предыдущий пример, задачу размещения элемента в памяти в рамках небезопасного контекста можно упростить с помощью ключевого слова stackalloc. В силу самой природы этой операции соответствующая память очищается, как только происходит возврат из метода размещения (поскольку память выбирается из стека). Но рассмотрим более сложный пример. В процессе обсуждения операции -› вы создали характеризуемый значением тип Point. Подобно всем типам, характеризуемым значениями, выделенная для него память в стеке освобождается сразу же после исчезновения контекста выполнения. Теперь, для примера, предположим что тип Point был определен как ссылочный тип.

class Point { //‹= Теперь это класс!

 public int x;

 public int у;

 public override string ToString { return string.Format("({0}, {1})", x, y); }

}

Вы хорошо знаете о том, что если вызывающая сторона объявляет переменную типа Point, для нее выделяется динамическая память, являющаяся объектом для сборки мусора. Тогда возникает вопрос: что произойдет, если небезопасный контекст попытается взаимодействовать с соответствующим объектом (или любым другим объектом в динамической памяти)? Поскольку сборка мусора может начаться в любой момент, представьте себе всю болезненность доступа к членам Point, когда идет очистка динамической памяти. Теоретически возможно, что небезопасный контекст будет пытаться взаимодействовать с членом, который больше не доступен или занимает новое положение в динамической памяти, "пережив" очередную генерацию чистки (и это, очевидно, является проблемой).

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