Работа с небезопасным кодом в C#

Добавлен , опубликован
Раздел:
C#
Язык C# поддерживает указатели, однако несколько ограниченно. Ограниченность заключается в том, что применение указателей не поощряют, поскольку справедливо считается, что это может повлиять на надежность как кода, так и среды выполнения в целом.
Указатель - это переменная, содержащая в себе адрес памяти, в которой содержится переменная какого-либо типа. Другое ограничение C# - указатели могут быть объявлены только для удержания адреса переменной и массива. В отличие от ссылочных типов, типы указателей не отслеживаются механизмом сбора мусора по умолчанию. По той же самой причине указателям не разрешено указывать на ссылочный тип или даже на тип структуры, которая содержит в себе ссылочный тип. Можно сказать, что указатели могут указывать только на неуправляемые типы, которые включают в себя все базовые типы данных, типы перечисления, другие типы указателей и структуры, которые содержат только неуправляемые типы.
Чтобы работать с неуправляемыми конструкциями, код должен компилироваться с ключом /unsafe.
Пример:
Правый клик по проекту (не решению) -> Свойства -> Сборка -> Разрешить небезопасный код
Затем сохраняемся, чтобы применить изменения к проекту.

Объявление указателя

Основная форма объявления переменной указателя следующая:
type *variable_name;
Здесь звездочка * обозначает оператор разыменования. Например, строка
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# определено, что операторы могут выполняться либо в безопасном, либо в небезопасном контексте. Операторы, процедуры и функции, помеченные как небезопасные, запускаются вне области управления памятью с помощью сборщика мусора. Помните, что любой код 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();
   }
}

Прикрепление объекта

Сборщик мусора C# может переместить объекты в памяти в соответствии с алгоритмом процесса уборки мусора. Язык C# предоставляет специальное ключевое слово fixed, чтобы указать сборщику мусора не перемещать объект. Это означает, что позиция переменной в памяти фиксируется, чтобы на нее мог ссылаться указатель. В C# это называется прикреплением.
Функционал оператора fixed обычно реализован путем генерации таблиц, описывающих для сборщика мусора, какие объекты в каких областях выполняемого кода должны оставаться фиксированными. Таким образом, пока процесс сбора мусора не встречает во время выполнения операторов fixed, потери ресурсов на них оказываются весьма незначительными. Однако, когда сборщик мусора встречает fixed, то фиксированные объекты могут привести к образованию фрагментации кучи. Т. е. в куче могут появиться неиспользуемые "дыры". Следовательно, объекты должны использовать fixed только тогда, когда это абсолютно необходимо, и только на самый малый, насколько это возможно, промежуток времени выполнения кода.

Указатели и методы

Указатели могут быть переданы в метод как аргументы. Методы также могут возвратить указатель. Пример:
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();
   }
}

Указатели и преобразования типа

Типы указателей в C# не наследуются от объекта, и нет существующих преобразований между типами указателя и объектами. Это означает, что boxing и un-boxing не поддерживается указателями. Однако C# поддерживает преобразования между различными типами указателей, типами указателей и целочисленными типами.
C# поддерживает и неявные, и явные преобразования указателя в небезопасном контексте. Имеются неявные преобразования типа:
  • Из типа указателя на любой тип к типу указателя на тип void *.
  • Из типа null к любому другому типу указателя.
Оператор преобразования типа cast operator () необходим для любых явных преобразований типа. Имеются явные преобразования типа:
  • Из любого типа указателя на любой другой тип указателя.
  • Из типов sbyte, byte, short, ushort, int, uint, long, ulong к любому другому типу указателя.
  • Из любого типа указателя к типам sbyte, byte, short, ushort, int, uint, long, ulong.
Пример:
char c = 'R';
char *pc = &c;
void *pv = pc;        // неявное преобразование
int *pi = (int *) pv; // явное преобразование оператором кастинга

Арифметика указателей

В небезопасном контексте, операторы ++ и -- могут быть приложены к переменной указателя всех типов, за исключением типа void *. Таким образом, для каждого типа указателя T* следующие операторы будут неявно перегружены.
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#.

Выделение памяти стека

В небезопасном контексте локальные определения переменных могут включать инициализатор выделения стека, который выделяет память из стека вызовов.
Оператор stackalloc T[E] требует T как необрабатываемый тип и E как выражение типа int. Вышеуказанная конструкция выделяет E * sizeof(T) байт из стека и генерирует указатель типа T* на новый выделенный блок. Если E отрицательно, то выбрасывается исключение System.OverFlowException. Если недостаточно памяти, то срабатывает исключение System.StackOverflowException.
Содержимое только что выделенной памяти является неопределенным. Нет способа неявного освобождения памяти, выделенной через stackalloc. Вместо этого весь блок памяти стека автоматически освобождается после возврата из функции.

Указатели и массивы

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

Указатели и структуры

Все структуры 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(); 
   }
}
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
0
26
6 лет назад
0
uranus, быстрее чем функции стандартной библиотеки можно добиться и не прибегая к небезопасному коду (написать собственную реализацию), тут о другом речь.
0
29
6 лет назад
0
Мне кажется или это похоже на C++ в C#?
Не знаю, я пользуюсь mem alloc'ом в некоторых своих подопытных детишках по C++ только...
У меня свои шаманства с бубном, короче.
0
26
6 лет назад
0
KingMaximax, какой-то странный вопрос. Синтаксически C# происходит от C, что вроде очевидно. Соответсвенно и форма записи указателей взята оттуда.
Но несмотря на внешнюю схожесть, внутри C# работает абсолютно по-другому. Даже в небезопасном контексте с прямым доступом к памяти, код все равно выполняется на виртуальной машине.
0
29
6 лет назад
0
Hanabishi, т.е. он пытался обойти виртуальную машину, если я правильно понял. Это как внутренняя система безопасности проверки жизненности цикла программы, что-то наподобие в этом роде. Она добавляет 100+ лишних килобайт. Но её вроде отключать можно, но лучше не надо, иначе есть риск поменять ОЗУ. Мой продвинутый друг в программировании занимался подобным, но потом перешёл на ассемблер из-за паранойности C++\C#, врать не буду, но это не совсем точные сведения. Или я снова туплю?
0
26
6 лет назад
0
KingMaximax, неправильно, нельзя "обойти виртуальную машину", код в любом случае на ней выполняется. Это главное отличие C# от нативных C\C++, но речь в статье вообще не об этом.
Есть понятие управляемого и неуправляемого кода. Так вот шарп по-умолчанию использует управляемый код, это когда все данные жестко контролируются средой, ты не управляешь памятью напрямую и не можешь критически накосячить, а ненужные данные удаляются автоматически (то есть утечки памяти практически невозможны).
Но существует и возможность работать с неуправляемым кодом, использовать прямой доуступ к памяти, о чем собственно статья. Это считается небезопасным (отсюда и ключевое слово unsafe), потому что работоспособность, стабильность и защищенность программы начинает на 100% зависеть от уровня криворукости программиста.
0
29
6 лет назад
0
Hanabishi, теперь понял. Теперь мне придётся пересмотреть некоторые темы программирование, а то уже совсем что-то забываю. Уже начал путать эмулятор со строгим оптимизатором, грубо говоря.
И ещё, извиняюсь за свою неграмотность в области ЯП.
0
29
6 лет назад
0
Все структуры C# имеют тип переменной
Это так переведено all C# structs are value types?
0
29
6 лет назад
0
Doc, "Все структуры C# имеют значимый тип". Пофикшено.
0
29
6 лет назад
0
Msey:
Самый частый случай, который встречался мне на практике - это более шустрые операции с массивами и строками за счет отсутствия проверки их границ.
оптимизация работы с большими массивами данных, когда надо тоскать между функциями все эти данные и манипулировать небольшими участками массива без выделения в небольшой кусочек.
Но с недавних пор это все не актуально стало. В новом стандарте есть новые структуры данных, которые оборачивают такие небезопасные операции и непосредственное использование unsafe становится ненужным
Этот комментарий удален
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.