首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Pins基础字符串的GetPinnableReference实现

Pins基础字符串的GetPinnableReference实现
EN

Stack Overflow用户
提问于 2018-10-09 17:54:47
回答 1查看 1.5K关注 0票数 3

我正在尝试实现C# 7.3中引入的新模式,它支持使用fixed语句固定自定义类型。见关于Docs的文章

但是,我担心的是,在下面的代码中,我返回一个指向字符串的指针,然后离开fixed作用域。当然,整个例程将由“父”fixed语句使用,该语句将“修复”我的自定义类型,但是我不确定字符串(它是我的自定义类型中的字段)是否仍然是固定的。所以我不知道这整个方法会不会奏效。

代码语言:javascript
复制
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);
  }
}

下面的示例代码将使用上述代码:

代码语言:javascript
复制
static void SomeRoutine(PinnableStruct data) {
  fixed(char* p = data) {
    //iterate over characters in data and do something with them
  }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-10-24 19:15:03

在这一点上不需要固定字符串或指针,实际上,这样做在概念上是不正确的。在空检查/返回(这也应该检查长度为0)之后,您可以这样做:

代码语言:javascript
复制
var strSpan = _String.AsSpan();
return ref strSpan[_Index];

正如函数名称所指定的那样,这将返回对基础字符串的第一个字符的可定位引用。上面的内容返回了对指向基础字符串的固定指针的第一个取消引用元素的引用,但是钉住的生存期仅限于固定语句的范围(可能是错误的,但我不认为返回的引用保持固定)。对Span元素使用ref可以避免不必要的钉扎。

更新--我更多地研究了一些固定的指针(一堆毫无成效的google搜索--回到了反编译/实验中)。结果,将引用返回到固定指针的元素并不会保留引脚。返回的引用是对已解析地址的引用--所有固定的上下文都丢失了。

我想也许可以将_String更改为Memory<char> (在构造函数中使用AsMemory扩展)。然后,您的GetPinnableReference可以只是return ref _String[0];,但是AsMemory为您提供了一个只读内存对象(不能返回可写引用)。

除了用GCHandleIDisposableMemoryManager<T>实现GCHandleIDisposableMemoryManager<T>(肯定比您想要的要重)外,我认为上面的Span<char>解决方案可能是实现这一目标的最安全/正确的方法。

下面是GetPinnableReference的反汇编(不要介意我的PowerPC.Test命名空间--我正在制作一个.NET标准1.1 PowerPC二进制反汇编程序/模拟器/调试器,并大量使用这些概念,因此我感兴趣):

代码语言:javascript
复制
.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_0string pinned V_1char& V_2。对我来说,这说明了为什么固定的语法是这样的。您有由string pinned V_1表示的固定关键字,char* V_0表示的指向char的指针(从代码中),以及由char& V_2表示的返回的引用。由三个独立的代码结构表示的三个独立的概念。

因此,您的固定语句会将字符串引脚并将V_1的固定引用赋给IL_0007,然后加载V_1将其转换为指针(转换为无符号整数)并将其存储在IL_0008IL_000aV_0 ( char* p)中,最后加载V_0,计算_index指定的char的偏移量,并将其存储在V_0IL_000eIL_0015中。在固定语句的范围内,它加载V_0,通过Unsave.AsRef<char>将其转换为引用,并将其存储在IL_0017IL_001dV_2 (将返回的引用)中。我不知道C#编译器为什么要这样做,但是从IL_001eIL_0021的最后一点通常是编译器如何处理返回值的问题;它将一个分支插入到将返回值的块中(即使在本例中是下一个指令),然后它从一个局部变量加载返回值(它创建了一个局部变量,而even必须分配给它,但也许对此有一个逻辑解释?),并最终返回您在前几个指令中获得的结果。

你终于找到答案了!固定语句中的钉扎无法维护,因为它取消了非托管指针V_0/p的引用(在本例中这种情况下发生这种情况是由固定引用V_1支持的,这是指针不知道的取消引用的指令)。CLR团队在JIT中可能很聪明,并通过将char引用与固定引用相关联的分析来特别处理该案例,但我认为这在架构上很奇怪,很难解释/文档,而且我怀疑他们走了这条路,所以我没有查看CLR代码来验证。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52726779

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档