首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在不复制结构的情况下在结构上获取Span<byte>

在不复制结构的情况下在结构上获取Span<byte>
EN

Stack Overflow用户
提问于 2020-01-05 01:41:53
回答 2查看 1.8K关注 0票数 5

我一直在试验Span<T>作为ReadOnlySequence<T>和System.IO.Pipelines的一部分。

我目前正在尝试在不使用Span<T>代码的情况下在struct上获得一个struct,并且不复制该struct

我的结构很简单:

代码语言:javascript
复制
    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)]
    public struct Packet
    {
        public byte TestByte;
    }

方法1-有效-但感觉“不安全”

代码语言:javascript
复制
    //
    // Method 1 - uses Unsafe to get a span over the struct
    //
    var packet = new Packet();
    unsafe
    {
        var packetSpan = new Span<byte>(&packet, Marshal.SizeOf(packet));

        packetSpan[0] = 0xFF; // Set the test byte
        Debug.Assert(packet.TestByte == 0xFF, "Error, packetSpan did not update packet.");
            // ^^^ Succeeds
        packet.TestByte = 0xEE;
        Debug.Assert(packetSpan[0] == 0xEE, "Error, packet did not update packetSpan.");
            // ^^^ Succeeds
    }

方法2-它不按预期工作,因为它需要一个副本

代码语言:javascript
复制
    //
    // Method 2
    //
    // This doesn't work as intended because the original packet is actually
    // coppied to packet2Array because it's a value type
    //
    // Coppies the packet to an Array of Packets
    // Gets a Span<Packet> of the Array of Packets
    // Casts the Span<Packet> as a Span<byte>
    //
    var packet2 = new Packet();

    // create an array and store a copy of packet2 in it
    Packet[] packet2Array = new Packet[1];
    packet2Array[0] = packet2;

    // Get a Span<Packet> of the packet2Array
    Span<Packet> packet2SpanPacket = MemoryExtensions.AsSpan<Packet>(packet2Array);

    // Cast the Span<Packet> as a Span<byte>
    Span<byte> packet2Span = MemoryMarshal.Cast<Packet, byte>(packet2SpanPacket);

    packet2Span[0] = 0xFF; // Set the test byte
    Debug.Assert(packet2.TestByte == 0xFF, "Error, packet2Span did not update packet2");
        // ^^^ fails because packet2 was coppied into the array, and thus packet2 has not changed.
    Debug.Assert(packet2Array[0].TestByte == 0xFF, "Error, packet2Span did not update packet2Array[i]");
        // ^^^ succeeds

    packet2.TestByte = 0xEE;
    Debug.Assert(packet2Span[0] == 0xEE, "Error, packet2 did not update in packet2Span");
        // ^^^ fails because packet2Span is covering packet2Array which has a copy of packet2 
    packet2Array[0].TestByte = 0xEE;
    Debug.Assert(packet2Span[0] == 0xEE, "Error, packet2 did not update in packet2Span");
        // ^^^ succeeds

进一步的研究表明,

Span<T>可以从byte[]隐式转换,例如,我可以这样做

代码语言:javascript
复制
Span<byte> packetSpan = new Packet().ToByteArray();

但是,我所拥有的ToByteArray()的任何当前实现都仍然在复制数据包结构。

我不能做这样的事:

代码语言:javascript
复制
Span<byte> packetSpan = (byte[])packet;
    // ^^ Won't compile
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-01-05 02:33:53

如果没有Span<byte>,就无法在任意的结构上获取unsafe,因为这样的跨度将允许您以任何方式更改结构的任何一点,可能会违反类型的不变量--这本质上是不安全的操作。

好吧,但是ReadOnlySpan<byte>呢?请注意,您必须将StructLayoutAttribute放在您的结构中,才能使代码变得合理。这应该是个暗示。想象一下,试图编写一个更简单的方法,一个返回任意byte[]T where T : struct的方法。你必须先找出struct的大小,不是吗?那么,如何在struct中找出C#的大小呢?您可以使用sizeof操作符,它需要一个unsafe上下文,并且需要该结构成为一个非托管类型;或者可以使用Marshall.SizeOf,它不稳定,只适用于具有顺序或显式字节布局的结构。没有安全、通用的方法,因此您不能这样做。

Span<T>ReadOnlySpan<T>的设计并不是为了访问结构化字节,而是使用数组的生成片段,这些片段具有已知的大小,并且保证是连续的。

如果您确信自己知道自己在做什么,那么您可以在unsafe上下文中这样做--这就是它的目的。但是请注意,基于上述原因,您使用unsafe的解决方案不会泛化为任意的结构。

如果您打算将您的结构用作IO操作的缓冲区,则可能需要查看固定大小缓冲器。它们还需要一个unsafe上下文,但您可以在结构中封装不安全的内容,并将Span<byte>返回到该固定缓冲区。基本上,处理内存中对象的字节结构的任何东西都需要unsafe中的.NET,因为内存管理就是这个“安全”所指的东西。

票数 4
EN

Stack Overflow用户

发布于 2020-01-05 09:39:45

你必须在一个不安全上下文中这样做,因为它是不安全的,从这个词的真正含义来看,因为如果你不够小心,你会朝自己的脚开枪。原因如下:

考虑以下代码:

代码语言:javascript
复制
Span<byte> GiveMeSpan() 
{
    MyLovelyStruct value = new MyLovelyStruct();
    unsafe 
    {
        return new Span<byte>(&value, sizeof(MyLovelyStruct));
    }
}

我们在GiveMeSpan()中创建的GiveMeSpan()实例存在于方法的调用堆栈中,您要做的是获取它的地址,将其交给Span<byte>,并返回Span<byte>。一旦一个方法返回,它会弹出它的堆栈框架,因此您的MyLovelyStruct所处的内存将变得空闲,并可能被调用者调用并损坏它的下一个方法所回收。

但这还不是全部,如果您的MyLovelyStruct生活在这样的对象字段中:

代码语言:javascript
复制
class MyLovelyClass 
{
    private MyLovelyStruct value;

    public void Foo() 
    {
        unsafe 
        {
            var span = new Span(&value, sizeof(MyLovelyStruct));
            Process(span);
        }
    }
}

// Declaration 
Process(Span<byte> span);

GC方法正在处理您的MyLovelyStructMyLovelyClass突然被移动到内存中时(是的,GC移动内存中的对象,在这里读),就会发生MyLovelyStruct?是的,指向MyLovelyStructSpan<byte>将不再指向新的MyLovelyStruct地址,您的程序就会损坏。

因此,为了安全地使用struct或任何其他指针类型包装Span<byte>,您必须确保:

  • 实例位于固定的内存位置(例如,在堆栈或非托管内存中,如Marshal.AllocHGlobal分配的内存块)。
  • 在完成指针操作之前,将不会调用实例内存。

因此,unsafe关键字是必需的,即使您可以绕过它,也要由您来警告读者您的代码。

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

https://stackoverflow.com/questions/59596364

复制
相关文章

相似问题

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