Marshal.SizeOf 和 sizeof 的区别,我就是不明白

oli*_*ver 7 c# unsafe unmanaged

直到现在我才认为 Marshal.SizeOf 是计算非托管堆上 blittable 结构的内存大小的正确方法(这似乎是 SO 上的共识以及网络上几乎所有其他地方的共识)。

但是在阅读了一些针对 Marshal.SizeOf 的警告之后(这篇文章在“但是有一个问题......”之后)我尝试了一下,现在我完全困惑了:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这里出了什么问题?字段取消引用运算符 '->' 假定什么内存布局?'->' 甚至是寻址非托管结构的正确运算符吗?或者 Marshal.SizeOf 是非托管结构的错误大小运算符?

我没有发现任何可以用我理解的语言解释这一点的内容。除了“......结构布局无法发现......”和“......在大多数情况下......”诸如此类的东西。

Héc*_*rán 6

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

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

  • 嗯,是的,这就是我已经在各处读过的内容。但这并不能解释为什么我可以在 16 位分配的内存中存储(总共)32 位值。听起来好得令人难以置信,但也许我应该将其作为一种商业模式进行探索……;-) (2认同)
  • @oliver 现在尝试 `Unsafe.SizeOf<TestStruct>()` 或 `sizeof(TestStruct)`;他们返回`4` (2认同)
  • @oliver 访问冲突是……棘手的;我不确定是否很难保证发现每个场景 - 非托管内存本质上就是这样麻烦:) (2认同)

zzx*_*xyz 3

我认为您仍然没有回答的一个问题是您的特定情况下发生了什么:

\n\n
&ps2->x\n0x02ca4370  <------\n    *&ps2->x: 0xabcd '\xea\xaf\x8d'\n&ps2->y\n0x02ca4372  <-------\n    *&ps2->y: 0x1234 '\xe1\x88\xb4'\n
Run Code Online (Sandbox Code Playgroud)\n\n

您正在向(可能)未分配的内存写入和读取。由于您所在的内存区域,它未被检测到。

\n\n

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

\n\n
  TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize*10000);\n\n  // hmmm, put to 16 bit numbers into only 2 allocated \n  // bytes, this must surely fail...\n  for (int i = 0; i < 10000; i++)\n  {\n    ps2->x = (char)0xABCD;\n    ps2->y = (char)0x1234;\n    ps2++;\n  }\n
Run Code Online (Sandbox Code Playgroud)\n