问题 结构中C#fixed bool数组的大小和对齐是什么?


进行P / Invoke时,重要的是使数据布局匹配。

我们可以使用一些属性来控制struct的布局。

例如:

struct MyStruct 
{
    public bool f;
}

给出大小为4.虽然我们可以告诉编译器使它成为1字节bool来匹配C ++类型 bool

struct MyStruct
{
    [MarshalAs(UnmanagedType.I1)]
    public bool f;
}

给出1的大小。

这些都有道理。但是当我测试固定的bool阵列时,我很困惑。

unsafe struct MyStruct
{
    public fixed bool fs[1];
}

给出4个字节的大小。和

unsafe struct MyStruct
{
    public fixed bool fs[4];
}

仍然给出4个字节的大小。但

unsafe struct MyStruct
{
    public fixed bool fs[5];
}

给出8的大小。

它看起来像在固定的bool数组中,bool元素的大小仍然是1个字节,但是对齐是4个字节。这与C ++ bool数组不匹配,后者是1字节大小和对齐方式。

有人能解释一下吗?

更新:我终于找到了,原因是,bool类型在一个结构中,然后该结构将永远不会被blittable!因此,不要指望内部具有bool类型的结构与C中的布局相同。

问候, 翔。


3613
2018-01-07 05:20


起源



答案:


一个 布尔 这是相当特殊的,它可以追溯到Dennis Ritchie决定不给C语言一个bool类型。这引起了大量的混乱,语言和操作系统设计者自己添加了它并做出了不兼容的选择。

它作为BOOL typedef添加到Winapi中。如果您不强制使用其他类型,则这是默认的封送处理。类型定义为 INT 为了使它与C兼容,你发现需要4个字节。正如你所发现的那样,与任何一个对齐 INT 确实。

它被添加到C ++中。没有大小规范,大多数C ++编译器实现选择单个字节进行存储。最值得注意的是Microsoft C ++编译器,你最可能实现的实现。

它作为VARIANT_BOOL添加到COM Automation中。最初的目标是作为Visual Basic的新扩展模型来摆脱VBX限制,它变得非常流行,而且几乎所有Windows上的语言运行时都支持它。 VB当时受16位操作系统敏感度的影响很大,VARIANT_BOOL需要2个字节。

所有三个本机运行时环境都可能是C#程序中互操作的目标。显然,CLR设计人员很难做出选择,必须选择1,2和4个字节。没有办法获胜,而CLR确实有机会猜测COM互操作,它无法知道你是否尝试与基于C的api或C ++程序互操作。所以他们做出了唯一合乎逻辑的选择:没有一个。

包含bool的结构或类类型永远不会 blittable。即使应用[MarshalAs(UnmanagedType.U1)],也不会使它与CLR类型兼容。不太确定这是一个好的决定,但它是他们制作的那个,所以我们必须处理它。

获得一个blittable结构是非常需要的,它避免了复制。它允许本机代码直接访问托管堆和堆栈。非常危险,许多破坏的pinvoke声明已经损坏了GC堆,没有通常的好处 不安全 关键字提醒。但是不可能为速度而战。

你得到一个blittable结构  运用 bool。使用 byte 代替。您仍然可以通过使用属性包装struct成员来获取bool。不要使用自动实现的属性,您必须关心字节的位置。从而:

struct MyStruct 
{
    private byte _f;
    public bool f {
        get { return _f != 0; }
        set { _f = value ? 1 : 0; }
    }
}

本地代码不知道该属性。不要担心getter和setter的运行时开销,抖动优化器会使它们消失,并且它们每个都变为单个CPU指令。


13
2018-01-07 07:41



private bool _f;  - > private byte _f;? - PetSerAl
很高兴我解释得很好:)谢谢。 - Hans Passant
谢谢,这是一个非常详细的解释。虽然我理解C ++中的属性可能是一个强大的解决方案,但我仍然想知道为什么在C#中修复bool数组,它的行为非常奇怪,看起来它的大小是1个字节,但它的对齐是4个字节。你知道嵌入在struct中的固定bool数组的C#编组背后有什么魔力吗? - Xiang Zhang
固定缓冲区是CLR中非常奇怪的动物,它被视为没有成员的自定义值类型。只是一团记忆。解释了缓冲区元素类型的强大限制,编译器需要在编译时知道大小,以便正确索引blob。只有简单的值类型才能实现。与C ++ / CLI语言更相关,这种值类型可以存储本机C ++对象。这是对齐选择的可能原因。理想情况下它会像C ++对象一样对齐到8,但是32位CLR不能提供这种保证,它只支持4。 - Hans Passant
但我检查了不安全的struct {fixed short f [1]},大小为2,{fixed byte f [1]},大小为1.固定数组的所有数值类型都按预期工作,但布尔类型除外。我认为bool也应该是非常基本的类型,它可以是4byte或1字节,但从我的观察来看,它是1号并且对齐4,这很奇怪。无论如何,谢谢你的解释。 - Xiang Zhang


答案:


一个 布尔 这是相当特殊的,它可以追溯到Dennis Ritchie决定不给C语言一个bool类型。这引起了大量的混乱,语言和操作系统设计者自己添加了它并做出了不兼容的选择。

它作为BOOL typedef添加到Winapi中。如果您不强制使用其他类型,则这是默认的封送处理。类型定义为 INT 为了使它与C兼容,你发现需要4个字节。正如你所发现的那样,与任何一个对齐 INT 确实。

它被添加到C ++中。没有大小规范,大多数C ++编译器实现选择单个字节进行存储。最值得注意的是Microsoft C ++编译器,你最可能实现的实现。

它作为VARIANT_BOOL添加到COM Automation中。最初的目标是作为Visual Basic的新扩展模型来摆脱VBX限制,它变得非常流行,而且几乎所有Windows上的语言运行时都支持它。 VB当时受16位操作系统敏感度的影响很大,VARIANT_BOOL需要2个字节。

所有三个本机运行时环境都可能是C#程序中互操作的目标。显然,CLR设计人员很难做出选择,必须选择1,2和4个字节。没有办法获胜,而CLR确实有机会猜测COM互操作,它无法知道你是否尝试与基于C的api或C ++程序互操作。所以他们做出了唯一合乎逻辑的选择:没有一个。

包含bool的结构或类类型永远不会 blittable。即使应用[MarshalAs(UnmanagedType.U1)],也不会使它与CLR类型兼容。不太确定这是一个好的决定,但它是他们制作的那个,所以我们必须处理它。

获得一个blittable结构是非常需要的,它避免了复制。它允许本机代码直接访问托管堆和堆栈。非常危险,许多破坏的pinvoke声明已经损坏了GC堆,没有通常的好处 不安全 关键字提醒。但是不可能为速度而战。

你得到一个blittable结构  运用 bool。使用 byte 代替。您仍然可以通过使用属性包装struct成员来获取bool。不要使用自动实现的属性,您必须关心字节的位置。从而:

struct MyStruct 
{
    private byte _f;
    public bool f {
        get { return _f != 0; }
        set { _f = value ? 1 : 0; }
    }
}

本地代码不知道该属性。不要担心getter和setter的运行时开销,抖动优化器会使它们消失,并且它们每个都变为单个CPU指令。


13
2018-01-07 07:41



private bool _f;  - > private byte _f;? - PetSerAl
很高兴我解释得很好:)谢谢。 - Hans Passant
谢谢,这是一个非常详细的解释。虽然我理解C ++中的属性可能是一个强大的解决方案,但我仍然想知道为什么在C#中修复bool数组,它的行为非常奇怪,看起来它的大小是1个字节,但它的对齐是4个字节。你知道嵌入在struct中的固定bool数组的C#编组背后有什么魔力吗? - Xiang Zhang
固定缓冲区是CLR中非常奇怪的动物,它被视为没有成员的自定义值类型。只是一团记忆。解释了缓冲区元素类型的强大限制,编译器需要在编译时知道大小,以便正确索引blob。只有简单的值类型才能实现。与C ++ / CLI语言更相关,这种值类型可以存储本机C ++对象。这是对齐选择的可能原因。理想情况下它会像C ++对象一样对齐到8,但是32位CLR不能提供这种保证,它只支持4。 - Hans Passant
但我检查了不安全的struct {fixed short f [1]},大小为2,{fixed byte f [1]},大小为1.固定数组的所有数值类型都按预期工作,但布尔类型除外。我认为bool也应该是非常基本的类型,它可以是4byte或1字节,但从我的观察来看,它是1号并且对齐4,这很奇怪。无论如何,谢谢你的解释。 - Xiang Zhang


应该管用:

[StructLayout(LayoutKind.Sequential)]
unsafe struct MyStruct
{
   public fixed bool fs[5];
}

-1
2018-01-07 18:02



我测试过,即使我添加了这个顺序布局属性,bool [3] size = 4,bool [4] size = 4,bool [5] size = 8,相同。 - Xiang Zhang
我也是:)你尝试过[StructLayout(LayoutKind.Sequential,Pack = 1)]吗?看起来在项目中的某个位置,您可以选择将结构的默认对齐方式设置为4个字节 - Dmitry