Указатели в C#Автор: Александр Игнатьев
Источник: http://www.daoto.net/,
http://docs.com.ru/Одним из преимуществ старого (неуправляемого) C++ - был прямой доступ к памяти, обеспечиваемый механизмом указателей. Это позволяло создавать высокпроизводительные приложения и являлось головной болью многих программистов. По себе знаю насколько было проблемно работать с ними. Многие программисты считают, что в C# не поддерживаются указатели. И называют это одним из его достоинств, позволяющим создавать безопасный, легко сопровождаемый код. На самом деле
в C# есть указатели, просто работа с ними несколько затруднена и ограничена по сравнению с C++.
Код, использующий указатели, должен компилироваться с указанием ключа
/unsafe:
В Visual Studio .NET этот параметр задается в
Properties проекта. На закладке
Build следует установить в
true параметр
Allow unsafe code blocks. Кроме того, метод в котором используются указатели должен быть помечен ключевым словом
unsafe.
Указатели могут объявлятся только на следующие типы:
• sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool.
• Перичисление.
• Определенная пользователем структура, содержащая только вышеперечисленные типы.
Указатель объявляется с помощью маркера
*:
Указатель в C#, так же как и в С++ представляет собой адрес в памяти. Получить адрес переменной можно с помощью маркера
&:
int i = 5;
int* p = &i;[/c]
В C# также поддерживается арифметика указателей, операторы инкремента и дикремента, оператор [b][][/b]:
[code]public unsafe static void Main(string[] args)
{
int i = 5;
int* p1 = &i;
Console.WriteLine("{0} {1} {2}", i, *p1, p1[0]);
//Out: 5, 5, 5
//следующие два присваивания равносильны
p1[1] = 6;
*(p1+1) = 6;
int* p2 = p1+1;
Console.Write("{0} {1}", *p2, p2[0]);
Console.WriteLine("{0} {1}", *(p1+1), p1[1]);
//Out: 6 6 6 6
p1++;
Console.Write("{0} {1} {2}", *p1, *p2, p1[-1]);
//Out: 6 6 5
}
Я думаю, понятно почему этот код называется опасным (unsafe). Где гарантия, что по измененному нами адресу
p1+1 не содержалась важная информация?
Для выделения памяти используется конструкция
stackalloc T[E] (правда, напоминает malloc в С?), где
T - один из вышеперечисленных типов. Эта конструкция резервирует память размером
E*sizeof(E):
int* pArr = stackalloc int[10];
Кроме того, указатели могут быть в качестве принимаемых и возвращаемых значаний функции:
public unsafe int* func(int* p, int i)
{
*p = i;
return p;
}
Конструкция fixedПри обращении к массиву в .NET проводится проверка индексов, что может привести к значительно снижению производительности. К примеру время рассчета одной механической модели методом Либмана:
C++ (VC++ 6.0) 1410 мс
C# (safe) 2910 мс
C# (unsafe) 1560 мс
Java 3100 мс
Как видите, отказ от проверки допустимости значений индекс дал выигрыш практически в 2 раза. И C# практически догнал по производительности C++. И это в реализации численного метода! Это говорит о высокой производительности C#.
Быстрый доступ к элементам массива (без проверки допустимости индексов) осуществляется следующим образом:
int[] narr = new int[100];
//объявляем указатель на первый элемент массива
int* p = &narr[0];
//и так можно:
//int* p = narr;
//теперь мы можем обратиться к любому элементу массива вот так
p[10]=p[9]+1;
//и без проверки границ!
//так что за последствия следующего кода будете отвечать сами:
p[200]=100;
А теперь представьте, объявили вы указатель на массив, работаете с ним. И тут запускается сборщик мусора, очищает память, деврагметирует ее. Интересно, а где теперь находится ваш массив? А кто его знает... Зато указатель указвает туда же. И все... Приехали. Дальше вы будете долбать данные своей проги, пока не нарветесь на исключение, а то вообще не нарветесь. Просто получите неверный результат, что гораздо хуже.
Вот для таких ситуаций и создана конструкция
fixed:
fixed(int* p = narr)
{
//а тут мы спокойно работаем с нашм указателем
//сборщик мусора шаш массив не тронет...
}
А как же быть с многомерыми массивами? А только так:
int Nx = 10, Ny = 15;
double[,] darr = new dpuble[Nx, Ny];
fixed(double* p = &darr[,])
{
//обращаемся к элементу darr[5,4]
double d = p[5*Ny + 4];
}
К сожалению именно так. А с jagged-массивами (double[][]) даже так не проходит...
Как видите, C# не очень хорошо приспособлен для работы с указателями. Да и в большинстве случаев можно обойтись без них, но если уж приспичит... Мне вот они понадобились...