tg-me.com/epeshkblog/155
Last Update:
GC Hole: чем ref отличается от указателя
GC Hole — баг, ситуация, когда в программе образуется ссылка на объект, уже собранный или перемещённый сборщиком мусора. Часто встречается в коде, который решили "микрооптимизировать".
Я никогда не встречал подобного бага вне кода, найденного в интернетах. Предположу, что его допускают в первую очередь программисты, переходящие на C# с языков с ручным управлением памятью — там аналогичная проблема называется use-after-free. По сути, GC Hole — частный случай use-after-free, только освобождение памяти выполняется не вручную, а сборщиком мусора.
Причина бага — попытка работать со ссылками на managed объекты как с адресами в памяти (числами).
Пример: long address = (long)Unsafe.AsPointer(ref value);
Пока на объект ссылаются обычными ссылками, или через `ref` — GC отслеживает эти ссылки и не будет собирать объект как мусор. При дефрагментации (compaction) кучи GC переместит объекты на новые места в памяти и обновит отслеживаемые им ссылки.void*
, int*
, IntPtr
, long
и подобные типы — просто числа, GC про них ничего не знает. В итоге, если между получением адреса и его использованием случится GC — программа может обратиться уже по некорректному адресу и прочитать/перезаписать данные из случайного места. Потенциально, GC Hole — уязвимость, security bug.
Проблема может долго оставаться незамеченной, например, просто везёт и GC не успевает сработать, или ссылка ведёт в Large Object Heap, который не дефрагментируется в нормальном режиме.
Как избежать GC Hole:
- не пытаться превращать managed ссылки в адреса-числа
- не делать оптимизаций, которые дают выигрыш по производительности в пределах погрешности: обычно нет смысла использовать указатели, чтобы убрать проверки на выход за границы массива и выиграть 0.01%
- если очень надо, например для интеропа, использовать пиннинг (запрет GC перемещать объект при дефрагментации), с помощью fixed
или GCHandle.Alloc(obj, GCHandleType.Pinned)
. Долгоживущие массивы можно аллоцировать в Pinned Object Heap
- осторожно работать с ref
. ref
может указывать на элемент массива или поле объекта
- использовать методы, работающие с ref
, которые позволяют обойтись без перехода к адресам-числам:
- Unsafe.IsAddressLessThan(ref a, ref b)
вместо Unsafe.AsPointer(ref a) < Unsafe.AsPointer(ref b)
- Unsafe.Add/Subtract
, Unsafe.ByteOffset
и подобные вместо арифметики указателей.
@epeshkblog | Поддержать канал
BY .NET epeshk blog
Share with your friend now:
tg-me.com/epeshkblog/155