`fixed` vs GCHandle.Alloc(obj, GCHandleType.Pinned)

Я попытался выяснить, как закреплены указатели с помощью ключевого слова fixed. Моя идея заключалась в том, что для этого был использован GCHandle.Alloc(object, GCHandleType.Pinned). Но когда я посмотрел на IL, сгенерированный для следующего кода С#:

unsafe static void f1()
{
  var arr = new MyObject[10];
  fixed(MyObject * aptr = &arr[0])
  {
     Console.WriteLine(*aptr);
  }
}

Я не мог найти никаких следов GCHandle.

Единственный намек, который я увидел, что приписанный указатель использовался в методе, был следующим объявлением IL:

.locals init ([0] valuetype TestPointerPinning.MyObject[] arr,
              [1] valuetype TestPointerPinning.MyObject& pinned aptr)

Таким образом, указатель был объявлен как закрепленный, и для этого не требовались дополнительные вызовы методов.

Мои вопросы

  • Есть ли разница между использованием закрепленных указателей в объявлении и фиксацией указателя с помощью GCHandle class?
  • Есть ли способ объявить привязанный указатель на С# без использования ключевого слова fixed? Мне нужно это, чтобы связать кучу указателей внутри цикла, и я не могу это сделать, используя фиксированное ключевое слово.

Ответ 1

Ну, конечно, есть разница, вы это видели. CLR поддерживает несколько способов привязки объекта. Только метод GCHandleType.Pinned напрямую подвергается действию кода пользователя. Но есть и другие, такие как "async pinned handleles" , функция, которая поддерживает буферизацию ввода-вывода, когда драйвер выполняет операцию перекрытия ввода-вывода. И тот, который использует фиксированное ключевое слово, он вообще не использует явный вызов дескриптора или метода. Эти дополнительные способы были добавлены, чтобы сделать повторное удаление объектов еще более быстрым и надежным, что очень важно для здоровья GC.

Фиксированные булавки буферов реализуются дрожанием. Который выполняет два важных задания, когда он переводит MSIL на машинный код, очень заметным является сам машинный код, вы можете легко увидеть его с помощью отладчика. Но он также генерирует структуру данных, используемую сборщиком мусора, полностью невидимую в отладчике. Требуется GC для надежного поиска обратных ссылок на объекты, которые хранятся в фрейме стека или в регистре CPU. Подробнее об этой структуре данных в этом ответе.

Джиттер использует атрибут [закрепленный] в объявлении переменной в метаданных, чтобы установить бит в этой структуре данных, указывая, что объект, на который ссылается переменная, временно привязан. GC видит это и знает, чтобы не перемещать объект. Очень эффективен, потому что он не требует явного вызова метода для выделения дескриптора и не требует хранения.

Но нет, эти трюки не доступны в противном случае для кода С#, вам действительно нужно использовать фиксированное ключевое слово в вашем коде. Или GCHandle.Alloc(). Если вы обнаруживаете, что теряетесь в булавках, то высокие шансы, что вы должны рассматривать pinvoke или С++/CLI, чтобы вы могли легко называть собственный код. Временные контакты, которые используется маркерщиком pinvoke для сохранения объектов в то время как собственный код работает, являются еще одним примером автоматического пиннинга, который не требует явного кода.