Язык C# также поддерживает указатели (pointer), однако несколько ограниченно. Ограниченность заключается в том, что применение указателей не поощряют, поскольку справедливо считается, что это может повлиять на надежность как кода, так и среды выполнения в целом.
Указатель - это всего лишь обычная переменная, содержащая в себе адрес памяти, в которой содержится переменная какого-либо типа (адрес переменной). Другое ограничение C# - указатели могут быть объявлены только для удержания адреса переменной и массива. В отличие от ссылочных типов (reference types), типы указателей (pointer types) не отслеживаются механизмом сбора мусора по умолчанию (default garbage collection). По той же самой причине указателям не разрешено указывать на ссылочный тип (reference type) или даже на тип структуры, которая содержит в себе ссылочный тип. Можно сказать, что указатели могут указывать только на неуправляемые типы (unmanaged types), которые включают в себя все базовые типы данных (basic data types), типы перечисления (enum types), другие типы указателей и структуры, которые содержат только неуправляемые типы.
[Объявление указателя]
Основная форма объявления переменной указателя следующая:
type *variable_name;
Здесь звездочка * обозначает оператор разыменования (de-reference operator). Например, строка
int *ptr;
объявляет переменную указателя ptr, которая может держать в себе адрес переменной типа int. Обратный оператор генерирования ссылки (reference operator, &) может использоваться для получения адреса переменной. Например, у нас есть переменная
int x = 100;
Оператор &x даст нам адрес переменной x, который мы можем присвоить переменной указателя.
int *ptr = &x;
Console.WriteLine((int)ptr); // Отобразится адрес памяти переменной.
Console.WriteLine(*ptr); // Отобразится значение переменной.
Здесь мы рассмотрели обычное простое использование указателей, применяемое на языках C и C++. На языке C# все происходит похожим образом, однако есть некоторые отличия.
[Небезопасный код (Unsafe Code)]
Правилами языка C# определено, что операторы могут выполняться либо в безопасном (safe), либо в небезопасном (unsafe) контексте. Операторы, процедуры и функции, помеченные как небезопасные, запускаются вне области управления памятью с помощью сборщика мусора (Garbage Collector). Помните, что любой код C#, использующий указатели, требует для выполнения небезопасный контекст.
Для того, чтобы пометить небезопасный контекст (т. е. код), используется ключевое слово unsafe. Мы можем использовать unsafe двумя различными способами. Ключевое слово unsafe может использоваться как модификатор метода, свойства, конструктора, и т. д. Например:
using System;
class MyClass {
public unsafe void Method()
{
int x = 10;
int y = 20;
int *ptr1 = &x;
int *ptr2 = &y;
Console.WriteLine((int)ptr1);
Console.WriteLine((int)ptr2);
Console.WriteLine(*ptr1);
Console.WriteLine(*ptr2);
} }
class MyClient {
public static void Main()
{
MyClass mc = new MyClass();
mc.Method();
} }
Кроме того, ключевое слово unsafe может также использоваться, чтобы пометить группу операторов как небезопасную:
using System;
class MyClass {
public void Method()
{
unsafe
{
int x = 10;
int y = 20;
int *ptr1 = &x;
int *ptr2 = &y;
Console.WriteLine((int)ptr1);
Console.WriteLine((int)ptr2);
Console.WriteLine(*ptr1);
Console.WriteLine(*ptr2);
}
} }
class MyClient {
public static void Main()
{
MyClass mc = new MyClass();
mc.Method();
} }
[Прикрепление объекта (Pinning an Object)]
Сборщик мусора C# может переместить объекты в памяти в соответствии с алгоритмом процесса уборки мусора. Язык C# предоставляет специальное ключевое слово fixed, чтобы указать сборщику мусора не перемещать объект. Это означает, что позиция переменной в памяти в памяти фиксируется, чтобы на нее мог ссылаться указатель. На C# это называется прикреплением (pinning).
Функционал оператора fixed обычно реализован путем генерации таблиц, описывающих для сборщика мусора, какие объекты в каких областях выполняемого кода должны оставаться фиксированными. Таким образом, пока процесс сбора мусора не встречает во время выполнения операторов fixed, потери ресурсов на них оказываются весьма незначительными. Однако, когда сборщик мусора встречает fixed, то фиксированные объекты могут привести к образованию фрагментации кучи (heap). Т. е. в куче могут появиться неиспользуемые "дыры". Следовательно, объекты должны использовать fixed только тогда, когда это абсолютно необходимо, и только на самый малый, насколько это возможно, промежуток времени выполнения кода.
[Указатели и методы (Pointers & Methods)]
Указатели могут быть переданы в метод как аргументы. Методы также могут возвратить указатель. Пример:
using System;
class MyClass {
public unsafe void Method()
{
int x = 10;
int y = 20;
int *sum = swap(&x,&y);
Console.WriteLine(*sum);
}
public unsafe int* swap(int *x, int *y)
{
int sum;
sum = *x + *y;
return ∑
} }
class MyClient {
public static void Main()
{
MyClass mc = new MyClass();
mc.Method();
} }
[Указатели и преобразования типа (Pointers & Conversions)]
Типы указателей в C# не наследуются от объекта, и нет существующих преобразований между типами указателя и объектами. Это означает, что boxing и un-boxing не поддерживается указателями. Однако C# поддерживает преобразования между различными типами указателей, типами указателей (pointer types) и целочисленными типами (integral types).
C# поддерживает и неявные (implicit), и явные (explicit) преобразования указателя в небезопасном контексте. Имеются неявные преобразования типа:
1. Из типа указателя на любой тип к типу указателя на тип void *. 2. Из типа null к любому другому типу указателя.
Оператор преобразования типа cast operator () необходим для любых явных преобразований типа. Имеются явные преобразования типа:
1. Из любого типа указателя на любой другой тип указателя. 2. Из типов sbyte, byte, short, ushort, int, uint, long, ulong к любому другому типу указателя. 3. Из любого типа указателя к типам sbyte, byte, short, ushort, int, uint, long, ulong.
Пример:
char c = 'R'; char *pc = &c; void *pv = pc; // неявное преобразование int *pi = (int *) pv; // явное преобразование оператором кастинга
[Арифметика указателей (Pointer Arithmetic)]
В небезопасном контексте операторы ++ и -- могут быть приложены к переменной указателя всех типов, за исключением типа void *. Таким образом, для каждого типа указателя T* следующие операторы будут неявно перегружены (implicitly overloaded).
T* operator ++ (T *x); T* operator -- (T *x);
Оператор ++ добавляет sizeof(T) к адресу, содержащемуся в переменной указателя, и оператор -- вычитает sizeof(T) из этого адреса для переменной указателя на тип T*.
In an un-safe context a constant can be added or subtracted from a pointer variable. Similarly a pointer variable can be subtracted from another pointer variable. But it is not possible to add two pointer variables in C#.
В небезопасном контексте операторы ==, !=, <, >, <=, >= могут также использоваться со значениями указателей на все типы. Умножение и деление переменной указателя на константу или другую переменную-указатель не поддерживается в C#.
[Выделение памяти стека (Stack Allocation)]
В небезопасном контексте локальные определения переменных могут включать инициализатор выделения стека (stack allocation initialiser), который выделяет память из стека вызовов (call stack).
Оператор stackalloc T[E] требует T как необрабатываемый (unmanaged) тип и E как выражение типа int. Вышеуказанная конструкция выделяет E * sizeof(T) байт из стека и генерирует указатель типа T* на новый выделенный блок. Если E отрицательно, то выбрасывается исключение System.OverFlowException. Если недостаточно памяти, то срабатывает исключение System.StackOverflowException.
Содержимое только что выделенной памяти является неопределенным. Нет способа неявного освобождения памяти, выделенной через stackalloc. Вместо этого весь блок памяти стека автоматически освобождается после возврата из функции.
[Указатели и массивы (Pointers & Arrays)]
В C# может быть получен доступ к элементам массива при помощи использованием нотаций указателя.
using System;
class MyClass {
public unsafe void Method()
{
int []iArray = new int[10];
for(int count=0; count < 10; count++)
{
iArray[count] = count*count;
}
fixed(int *ptr = iArray)
Display(ptr);
//Console.WriteLine(*(ptr+2));
//Console.WriteLine((int)ptr);
}
public unsafe void Display(int *pt)
{
for(int i=0; i < 14;i++)
{
Console.WriteLine(*(pt+i));
}
} }
class MyClient {
public static void Main()
{
MyClass mc = new MyClass();
mc.Method();
} }
[Указатели и структуры (Pointers & Structures)]
Все структуры C# имеют тип переменной. На структуру тоже может быть задан указатель только в том случае, если структура в качестве своих полей содержит только типы в виде значения. Пример:
using System;
struct MyStruct {
public int x;
public int y;
public void SetXY(int i, int j)
{
x = i;
y = j;
}
public void ShowXY()
{
Console.WriteLine(x);
Console.WriteLine(y);
} }
class MyClient {
public unsafe static void Main()
{
MyStruct ms = new MyStruct();
MyStruct *ms1 = &ms;
ms1->SetXY(10,20);
ms1->ShowXY();
} }
[Выводы]
1. Функции или процедуры C#, если они используют указатели, должны иметь атрибут unsafe.
private unsafe void DecodeALSEN(TAlsenStr* dst, pmi_ext_proto.TAnswRaw src)
{
byte pktnum = extproto.PacketNum(src);
if (0 == pktnum)
{
dst->CentralFrequency = src.alsen0.freq;
dst->ValueRMS = src.alsen0.rms;
}
else if (1 == pktnum)
{
dst->CodeChannel1 = src.alsen1.CodeChannel1;
dst->CodeChannel2 = src.alsen1.CodeChannel2;
}
}
2. Проект C#, в котором есть небезопасный (unsafe) код, должен иметь соответствующее разрешение в настройках (соответствует опции компилятора /unsafe).
3. Получение указателя от объекта возможно только в том случае, если он определен с атрибутом fixed.
fixed (TAlsenStr* pntAlsen = &sdata.ALSEN)
DecodeALSEN(pntAlsen, cbtmp);
[Как передать в функцию ссылку на массив данных в безопасном контексте?]
На языке C/C++ обычно массив большого размера передается через указатель. Однако в безопасном коде C# использовать указатели нельзя, возникает ошибка:
// Ошибка: Указатели и буферы фиксированного размера можно
// использовать только в небезопасном контексте:
UInt32 SumArray (byte *data, UInt32 len)
{
UInt32 sum = 0;
for(UInt32 i=0; i<len; i++)
sum += *data++];
return CRC8;
}
Для решения проблемы можно либо перевести проект в режим небезопасного (unsafe) кода, либо надо поменять синтаксис вызова функции:
UInt32 SumArray (byte [] data, UInt32 len)
{
UInt32 sum = 0;
for(UInt32 i=0; i<len; i++)
sum += data[i];
return sum;
}
[Ссылки]
1. Visual C#: небезопасный код. |