C# 结构体显式内存布局

因为需要在 C# 中处理类似 C 中的 Union 的数据结构,所以想通过显式指定结构体中字段的偏移量解决问题,结果却遇到了奇怪的问题

本问题在知乎提问,得到了网友@khm1600 的回复

1. 结构体定义#

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct MyStruct
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    [FieldOffset(2)]
    public byte[] Bytes;
}

2. 异常信息:#

TypeLoadException: 
未能从程序集“...”中加载类型“...MyStruct”,
因为它在 2 偏移位置处包含一个对象字段,
该字段已由一个非对象字段不正确地对齐或重叠。

经过测试,数组类型的偏移量为4的倍数就不会报错,所以它是按4字节对齐的吗?可是已经指定 Pack = 1 了啊。

3. 问题原因#

感谢 @khm1600

原始回答: 第一次回答 在我整理出4.1后的第二次回答

参考 信息技术 - 公共语言基础结构 (CLI) 分区 I 到 VI

3.1 FieldLayout#

定位到标准文件:II.22.16 FieldLayout : 0x10 图一

图二 @khm1600 在评论中前半句所指应该是图二第七条,但是后半句关于 uint nint 是 4字节 对齐的说法见内存模型高亮部分

3.2 关于nint#

3.2.1 CLI支持的数据类型#

定位到标准文件: I.12.1 Supported data types 图三 可以看到图三中 DataType 这列有 native int,描述为本机大小的二进制有符号值

3.2.2 本机类型大小#

image.png图四(两张图看作一张图)

PS:标准文件中搜索 nint 并没有搜到结果,但是在整型数值类型 - C# 参考中找到了相应的类型/关键字 C#预定义整型类型 平时基本上使用 C# 7.0 语法,好多新东西没学了😥

3.2.3 内存模型#

定位到标准文件 I.12.6 Memory model and optimizations 图五 图五选中部分解释了本机类型大小的自然对齐规则,即在 32位进程 为 4 字节,64位进程 为 8 字节

4. 示例代码#

[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct MyStruct
{
    public byte A;
    public short B;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public byte[] C;
}

//在解决方案平台 x64 下运行会出错,需要将C的Offset改为8
[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct MyStruct2
{
    [FieldOffset(0)] public byte A;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    [FieldOffset(4)] public byte[] C;
    [FieldOffset(1)] public short B;
}

[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct MyStruct3
{
    [FieldOffset(0)] public byte A;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    [FieldOffset(8)] public byte[] C;
    [FieldOffset(18)] public short B;
}

//测试值
A = 1,
B = 2,
C = new byte[10] { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 },

MyStruct

MyStruct2

MyStruct3

❤️ 如果这篇文章对你有帮助,欢迎赞助支持我继续维护 ❤️

☕ Support me ⚡ 爱发电赞助