Marshal.SizeOf与sizeof差异困惑:blittable结构体非托管内存计算疑问
为啥Marshal.SizeOf结果和sizeof不一样,而且非托管内存越界写没报错?
这个问题其实戳中了.NET里托管内存与非托管内存、类型封送的几个关键差异,咱们一步步拆解:
1. sizeof和Marshal.SizeOf根本不是一回事
先搞明白这俩方法的本质:
sizeof(TestStruct):计算的是托管堆里这个结构体实际占用的内存大小。C#里的char是固定的UTF-16编码(2字节),你的结构体包含两个char,加起来就是4字节,这个结果完全符合预期。Marshal.SizeOf(typeof(TestStruct)):计算的是**.NET把这个结构体封送到非托管内存时的默认尺寸**。默认情况下,.NET会把C#的char映射成非托管的ANSI字符(1字节),所以两个char的封送大小就是2字节。这不是结构体本身的内存大小,只是封送器默认规则下的非托管表示大小。
要是你想让Marshal.SizeOf返回和sizeof一致的4字节,给结构体加个特性就行:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct TestStruct { public char x; public char y; }
这样强制封送时使用UTF-16宽字符,Marshal.SizeOf就会返回4了。
2. 非托管内存越界写没报错?那是你运气好!
你用Marshal.AllocHGlobal只分配了2字节,却硬塞进去两个2字节的char,这妥妥是内存越界访问,属于不安全代码里的未定义行为:
- 非托管堆没有CLR那样严格的边界检查,它是直接调用系统API分配的内存,越界写入时,只要那块内存还属于当前进程(没被系统回收或分配给别的进程),就不会立刻触发访问冲突。
- 你这次没报错,只是刚好越界的那块内存暂时是空闲的,没被其他数据占用。但这绝对不是“正常工作”,要是后续这块内存被分配给别的变量使用,直接就会破坏数据,轻则程序崩溃,重则出现各种诡异的bug,排查起来极其困难。
说白了,这种写法是严重错误的,绝对不能这么干!
内容的提问来源于stack exchange,提问作者oliver




