Реализация GetPinnableReference, которая прикрепляет базовую строку - PullRequest
0 голосов
/ 09 октября 2018

Я пытаюсь реализовать новый шаблон, представленный в C # 7.3, который поддерживает закрепление пользовательских типов с помощью оператора fixed. См. Статью в Документах

Однако я обеспокоен тем, что в приведенном ниже коде я возвращаю указатель на строку, а затем покидаю область действия fixed .Конечно, вся эта подпрограмма будет использоваться «родительским» оператором fixed, который будет «исправлять» мой пользовательский тип, однако я не уверен, останется ли строка (которая является полем в моем пользовательском типе) фиксированной.,Поэтому я не знаю, сработает ли весь этот подход.

readonly struct PinnableStruct {

  private readonly string _String;
  private readonly int _Index;

  public unsafe ref char GetPinnableReference() {
    if (_String is null) return ref Unsafe.AsRef<char>(null);
    fixed (char* p = _String) return ref Unsafe.AsRef<char>(p + _Index);
  }
}

Приведенный выше код будет использоваться в следующем примере кода:

static void SomeRoutine(PinnableStruct data) {
  fixed(char* p = data) {
    //iterate over characters in data and do something with them
  }
}

1 Ответ

0 голосов
/ 24 октября 2018

Нет необходимости фиксировать строку или указатели на этом этапе, и на самом деле это было бы концептуально некорректно.После нулевой проверки / возврата (которая также должна проверять длину 0) вы можете сделать это:

var strSpan = _String.AsSpan();
return ref strSpan[_Index];

Это, как указывает имя функции, возвращает закрепляемую ссылку на первый символ базовой строки,То, что вы получили выше, возвращает ссылку на первый разыменованный элемент закрепленного указателя на нижележащую строку, но время жизни закрепления ограничено областью действия фиксированного оператора (это может быть неправильно, но я не думаю, чтовозвращенный реф держит его приколол).Использование ссылки на элемент Span позволяет избежать ненужного закрепления.

Обновление - я немного больше посмотрел на закрепленные указатели (куча бесполезных поисков в Google - вернулся к декомпиляции / экспериментам).Оказывается, возвращение ссылки на элемент закрепленного указателя не сохраняет этот вывод.Возвращенная ссылка является ссылкой на разрешенный адрес - весь контекст закрепления потерян.

Я подумал, что, возможно, изменение _String на Memory<char> (используйте расширение AsMemory в конструкторе) может работать.Тогда ваш GetPinnableReference может просто return ref _String[0];, но AsMemory дает вам объект памяти только для чтения (не может вернуть доступную для записи ссылку).

Если не реализовать IPinnable с GCHandle и IDisposable или MemoryManager<T> (тяжелее, чем вы хотите наверняка), я думаю, что мое решение Span<char>, описанное выше, может быть наиболее безопасным / правильным способом для достижения этой цели.

Вот разборка GetPinnableReference (не противмое пространство имен PowerPC.Test - я делаю бинарный дизассемблер / эмулятор / отладчик PowerPC .NET Standard 1.1 и использую эти концепции с большим интересом, поэтому я заинтересован):

.method public hidebysig instance char&  GetPinnableReference() cil managed
{
  // Code size       34 (0x22)
  .maxstack  2
  .locals init (char* V_0,
           string pinned V_1,
           char& V_2)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      string PowerPC.Test.Console.Program/PinnableStruct::_string
  IL_0007:  stloc.1
  IL_0008:  ldloc.1
  IL_0009:  conv.u
  IL_000a:  stloc.0
  IL_000b:  ldloc.0
  IL_000c:  brfalse.s  IL_0016
  IL_000e:  ldloc.0
  IL_000f:  call       int32 [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
  IL_0014:  add
  IL_0015:  stloc.0
  IL_0016:  nop
  IL_0017:  ldloc.0
  IL_0018:  call       !!0& [System.Runtime.CompilerServices.Unsafe]System.Runtime.CompilerServices.Unsafe::AsRef<char>(void*)
  IL_001d:  stloc.2
  IL_001e:  br.s       IL_0020
  IL_0020:  ldloc.2
  IL_0021:  ret
} // end of method SomeInfo::GetPinnableReference

Я не ожидаю, что большинствочитатели, чтобы иметь возможность читать IL, но это нормально.Ответ, который мы ищем, находится прямо вверху (и подтверждается в IL после него).У нас есть три местных жителя: char* V_0, string pinned V_1 и char& V_2.Для меня это проливает немного света на то, почему фиксированный синтаксис такой, какой он есть.У вас есть фиксированное ключевое слово, представленное string pinned V_1, указатель на символ (из вашего кода), представленный char* V_0, и возвращаемая ссылка, представленная char& V_2.Три отдельные концепции представлены тремя отдельными конструкциями кода.

Итак, ваш фиксированный оператор закрепляет строку и присваивает ссылку закрепления V_1 при IL_0002 IL_0007, затем загружает V_1, преобразует его в указатель (преобразование в целое число без знака) исохраняет его в V_0 (ваш char* p) в IL_0008 до IL_000a, наконец, он загружает V_0, вычисляет смещение char, указанное в _index, и сохраняет его обратно в V_0от IL_000e до IL_0015.В рамках фиксированного оператора он загружает V_0, преобразует его в ссылку с помощью Unsave.AsRef<char> и сохраняет ее в V_2 (ссылка, которая будет возвращена) с IL_0017 до IL_001d.Я не уверен, почему компилятор C # делает это, но последний бит от IL_001e до IL_0021 часто - то, как компилятор обрабатывает возвращение значения;он вставляет ветвь в блок, который будет возвращать значение (даже если в этом случае это следующая инструкция), затем он загружает возвращаемое значение из локальной переменной (которую он создал и IMO необязательно назначается, но, возможно, естьлогическое объяснение этому?) и, наконец, возвращает результат, который вы получили несколько инструкций назад.

Вот вам ответ, наконец-то!Невозможно сохранить закрепление в вашем фиксированном выражении, потому что оно разыменовывает неуправляемый указатель V_0 / p, который в данном случае просто поддерживается ссылкой закрепления, V_1, что является инструкцией для разыменованияуказатель не знаю о).Команда CLR могла бы быть умнее в JIT и обработать этот случай специально с помощью анализа, связав ссылку на символ с закрепленной ссылкой, но я думаю, что это было бы странно с архитектурной точки зрения, трудно объяснить / документировать, и я сомневаюсь, что они пошли такмаршрут, поэтому я не пошел, чтобы проверить код CLR.

...