Я реализую векторный тип на основе стека следующим образом:
[StructLayout(LayoutKind.Sequential)]
public struct VectorI16x8 {
public short s0, s1, s2, s3, s4, s5, s6, s7;
public short this[int i] {
get => UnsafeValues[i];
set => UnsafeValues[i] = value;
}
public int Length => 8;
private Span<short> UnsafeValues => MemoryMarshal.CreateSpan(ref s0, Length);
}
Совершенно очевидно, что если бы я сделал UnsafeValues
общедоступным, значения этой структуры можно было бы перемещать в памяти (даже без сборщика мусора), что приводило бы к висячей ссылке внутри Span
или к тому, что Span
указывало бы на неправильный экземпляр. .
Однако менее ясно, безопасно ли использование такого диапазона в моей реализации структуры. Мой вопрос: могу ли я предположить, что моя структура this
не будет перемещаться во время выполнения моего индексатора (это означает, что диапазон, созданный UnsafeValues
, останется действительным), или мне придется использовать блок fixed
, чтобы сделать это безопасным?
по касательной: возможно, вы захотите взглянуть на «встроенные массивы» — рассмотрите: [InlineArray(8)] public struct VectorI16x8 { private short _element0; }
— Sharplab.io/…
@MarcGravell Я знаю о встроенных массивах, но не могу использовать в Unity ничего более нового, чем .NET Standard 2.1. Кроме того, я хочу представить все значения как именованные поля, а также сделать их индексируемыми.
@Sinatr Я считаю, что это относится к структуре, представляющей массив фиксированного размера, что и есть. Но это не говорит мне о требованиях к закреплению для использования структуры в целом, и есть довольно большое предупреждающее сообщение о том, что время жизни возвращаемого диапазона не будет должным образом отслеживаться даже в языках, поддерживающих диапазоны.
нет никаких требований к закреплению — в этом весь смысл использования управляемых указателей; конечно, это предполагает, что Unity правильно понимает всю эту логику ;p
Для этого вам не требуется блок fixed
; короткая версия заключается в том, что для обработки этого сценария требуется сборщик мусора. Промежутки принципиально ничем не отличаются от любого другого «внутреннего указателя» — и та же проблема может быть инициирована каким-либо методом в вашей структуре, вызывающим DoSomething(ref s0)
, DoSomethingElse(in s1)
и т. д. Это одна из причин, почему управляемые указатели (ref
, Span<T>
и т. д.) могут быть только хранятся в стеке, а не как поля типов (кроме ref struct
) - чтобы сборщик мусора знал, где их проверять (ему в любом случае нужно пройти по стековым фреймам, проверить маркеры fixed
и т. д. - которые являются просто специальными токенами для локальных переменных) ). Справится ли сборщик мусора с этим, решив не перемещать эти регионы (как если бы они были fixed
) или исправить ссылки (во время паузы), является деталью реализации сборщика мусора.
(небольшое примечание: в зависимости от среды выполнения существует диапазон из 3 или 2 полей; они проектируются по-разному в зависимости от возможностей среды выполнения для обработки этого сценария, но в любом случае короткая версия - это «волшебство происходит» и это работает без паники)
Рад, что вы ответили вместо того, чтобы кто-то дал случайную чушь «типы значений находятся в стеке и не обрабатываются сборщиком мусора», которая игнорирует распространенные сценарии, такие как структуры как член класса.
Однако это делает то, что вы не можете сделать с обычной ссылкой: структурам не разрешено возвращать ссылки на себя или свои поля, но это фактически то, что делает UnsafeValues, поэтому мне интересно, имеет ли это значение? Кажется, это определенно было бы безопасно, если бы я встроил вызов MemoryMarshal.
@zstewart да, на этом основании могут быть некоторые проблемы - и действительно, ref
в CreateSpan
объявлен scoped
, так что можно надеяться, что компилятор здесь станет суетливым; однако, если там есть проблема: она не относится к fixed
vs not - и, скорее всего, связана с объявлением экранирования ссылки и повреждением стекового пространства из-за одновременного использования одного и того же фрагмента для несовместимых целей. Однако я не могу добиться сбоя локально.
Далее: учтите, что поля могут быть public
, и в этом случае вызывающий абонент может получить ref
к полям. Разные оттенки одной и той же проблемы.
Мой вывод таков: хотя компилятор не помешает мне создать висячую ссылку с помощью MemoryMarshal.CreateSpan
, пока ссылка все еще находится в живой памяти, GC не сломает ее.
В примечаниях к методу упоминается следующее: Этот метод может быть полезен, если часть управляемого объекта представляет собой фиксированный массив.