不安全代码出错,使用指针读取内存

时间:2014-04-21 06:11:07

标签: c# memory unsafe unsafe-pointers

我在内存中有一个二进制序列化对象,我想通过在C#中使用指针(unsafae代码)从内存中读取它。请查看从内存流中读取的以下函数。

static Results ReadUsingPointers(byte[] data)
{
    unsafe
    {
        fixed (byte* packet = &data[0])
        {
            return *(Results*)packet;
        }
    }
}

在此return *(Results*)packet;语句中,我得到编译时异常"无法获取地址,获取大小或声明指向托管类型的指针结果"

这是我的结构

public struct Results
{
    public int Id;
    public int Score;
    public char[] Product;
}

根据我的理解,我的struct的所有属性都是blittable属性,那么为什么我会收到这个错误,如果我需要在我的结构中使用char [],该怎么办?

修改-1 让我进一步解释(请注意,对象是模拟的)......

背景: 我有一个Results对象数组,我使用二进制序列化对它们进行了序列化。现在,在我的程序的后期阶段,我需要尽可能快地在内存中反序列化我的数据,因为数据量非常大。所以我在尝试,不安全的代码如何帮助我。

假设我的结构不包括public char[] Product;,我会以合理的速度恢复数据。但是使用char []它会给我错误(编译器应该这样做)。我想找到一个在这种情况下使用char []的解决方案。

2 个答案:

答案 0 :(得分:2)

MSDN says

  

以下任何类型都可以是指针类型:

     
      
  • sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal或bool。

  •   
  • 任何枚举类型。

  •   
  • 任何指针类型。

  •   
  • 任何用户定义的结构类型,仅包含非强制类型的字段

  •   

所以你可以定义你的结构如下来修复编译器错误:

public struct Results
{
    public int Id;
    public int Score;
    // Don't actually do this though.
    public unsafe char* Product;
}

这样,您可以指向数组的第一个元素。

但是,根据您编辑过的问题,您需要采用不同的方法。

  

我有一个Results对象数组,我使用二进制序列化对它们进行了序列化。现在,在我的程序的后期阶段,我需要尽快在内存中反序列化我的数据

通常您会将BinaryFormatter用于此目的。如果这太慢,那么问题应该是首先是否可以避免序列化。

答案 1 :(得分:1)

你不能指望这样做。

public struct Results
{
    public int Id;
    public int Score;
    public char[] Product;
}

char[]数组Product是托管类型。您的代码尝试使用类型Results*。那是指针类型。 documentation表示您可以声明指向以下任何内容的指针:

  
      
  • sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal或bool。
  •   
  • 任何枚举类型。
  •   
  • 任何指针类型。
  •   
  • 任何用户定义的结构类型,仅包含非托管类型的字段。
  •   

现在,你的结构清楚地匹配前三个子弹中的任何一个。并且与最终的子弹不匹配,因为数组是托管类型。

  

根据我的理解,我的struct的所有属性都是blittable属性。

是的,这是真的,但不相关。你需要结构的成员不仅仅是blittable。


即使您的代码会编译,您如何想象它可以工作?考虑一下这个表达式:

*(Results*)packet

编译器如何将其转换为可以创建新数组并复制数组正确数量元素的内容?很明显,编译器没有希望在这里做任何有用的事情,当然这也是语言拒绝你的代码的原因。

我不认为不安全的代码会帮助你。序列化数组时,必须序列化长度,然后序列化数组的内容。要反序列化,您需要读取长度,创建该长度的新数组,然后阅读内容。不安全的代码无法帮上忙。静态定义类型的简单内存副本是没有用的,因为这意味着数组的长度在编译时是已知的。事实并非如此。


关于您的更新,您说:

  

我有一个结果对象数组,我使用二进制序列化进行序列化。

为了反序列化,您需要能够理解二进制序列化详细布局的代码。问题中的代码无法做到。

您可能还没有理解的是,您不能指望复制任意内存块,其长度可变且仅在运行时已知,而实际上并不知道这些长度。实际上,您希望能够在系统中没有任何内容的情况下复制内存,知道要复制多少内容。

您尝试使用不安全的类型转换和内存复制进行反序列化是行不通的。如果不考虑序列化的二进制格式,就不能指望任何更详细的帮助。