Marshal.SizeOf和sizeof之间的区别,我只是不明白

时间:2018-03-23 23:58:59

标签: c# unmanaged unsafe

到目前为止,我刚刚理所当然地认为Marshal.SizeOf是计算非托管堆上blittable结构的内存大小的正确方法(这似乎是SO上和网上其他地方的共识)

但在阅读了对Marshal.SizeOf(this article之后的一些警告之后“但是有问题......”)我试了一下,现在我完全糊涂了:

public struct TestStruct
{
    public char x;
    public char y;
}

class Program
{
    public static unsafe void Main(string[] args)
    {
        TestStruct s;
        s.x = (char)0xABCD;
        s.y = (char)0x1234;

        // this results in size 4 (two Unicode characters)
        Console.WriteLine(sizeof(TestStruct));

        TestStruct* ps = &s;

        // shows how the struct is seen from the managed side... okay!      
        Console.WriteLine((int)s.x);
        Console.WriteLine((int)s.y);

        // shows the same as before (meaning that -> is based on 
        // the same memory layout as in the managed case?)... okay!
        Console.WriteLine((int)ps->x);
        Console.WriteLine((int)ps->y);

        // let's try the same on the unmanaged heap
        int marshalSize = Marshal.SizeOf(typeof(TestStruct));
        // this results in size 2 (two single byte characters)
        Console.WriteLine(marshalSize);

        TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize);

        // hmmm, put to 16 bit numbers into only 2 allocated 
        // bytes, this must surely fail...
        ps2->x = (char)0xABCD;
        ps2->y = (char)0x1234;

        // huh??? same result as before, storing two 16bit values in 
        // only two bytes??? next will be a perpetuum mobile...
        // at least I'd expect an access violation
        Console.WriteLine((int)ps2->x);
        Console.WriteLine((int)ps2->y);

        Console.Write("Press any key to continue . . . ");
        Console.ReadKey(true);
    }
}

这里出了什么问题?字段解除引用运算符' - >'的内存布局假设?是' - >'甚至是解决非托管结构的合适运营商?或者是Marshal.Size对于非托管结构的错误大小运算符?

我发现没有任何东西用我理解的语言解释这一点。除了“...结构布局是不可发现的......”和“......在大多数情况下......”这种过时的东西。

3 个答案:

答案 0 :(得分:3)

不同之处在于: sizeof 运算符采用类型名称,并告诉您需要为该结构的实例分配多少字节的托管内存。这不一定是堆栈内存;当结构元素是数组元素,类的字段等时,它们会从堆中分配。相比之下, Marshal.SizeOf 采用类型对象或类型的实例,并告诉您需要分配多少字节的非托管内存。由于各种原因,这些可能不同。该类型的名称为您提供了线索: Marshal.SizeOf 旨在将结构封送到非托管内存时使用。

两者之间的另一个区别是 sizeof 运算符只能取非托管类型的名称;也就是说,一个结构类型,其字段只是整数类型,布尔值,指针等。 (参见规范的确切定义。) Marshal.SizeOf 相比之下可以采用任何类或结构类型。

答案 1 :(得分:2)

我认为你还没有回答的一个问题是在你的特殊情况下会发生什么:

&ps2->x
0x02ca4370  <------
    *&ps2->x: 0xabcd 'ꯍ'
&ps2->y
0x02ca4372  <-------
    *&ps2->y: 0x1234 'ሴ'

您正在写入(可能)未分配的内存并从中读取。由于您所处的存储区域,因此无法检测到。

这将重现预期的行为(至少在我的系统上,YMMV):

  TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize*10000);

  // hmmm, put to 16 bit numbers into only 2 allocated 
  // bytes, this must surely fail...
  for (int i = 0; i < 10000; i++)
  {
    ps2->x = (char)0xABCD;
    ps2->y = (char)0x1234;
    ps2++;
  }

答案 2 :(得分:1)

  

字段解除引用运算符的内存布局&#39; - &gt;&#39;假设

无论CLI决定什么

  

是&#39; - &gt;&#39;甚至是解决非托管结构的合适运营商?

这是一个含糊不清的概念。通过CLI访问非托管内存中的结构:这些结构遵循CLI规则。并且有些结构只是非托管代码(可能是C / C ++)访问同一内存的名义标记。这遵循该框架的规则。编组通常是指P / Invoke,但这并不一定适用于此。

  

或者是Marshal.Size对于非托管结构的错误大小运算符?

我默认为Unsafe.SizeOf<T>,基本上是sizeof(T) - 这对于CLI / IL来说是完全明确定义的(包括填充规则等),但是不可能在C#。