C#中布尔值的大小是多少?它真的需要4个字节吗?

biv*_*biv 136 c# interop

我有两个带字节和布尔数组的结构:

using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] values;
}

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public bool[] values;
}
Run Code Online (Sandbox Code Playgroud)

以下代码:

class main
{
    public static void Main()
    {
        Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
        Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
        Console.ReadKey();
    }
}
Run Code Online (Sandbox Code Playgroud)

这给了我以下输出:

sizeof array of bytes: 3
sizeof array of bools: 12
Run Code Online (Sandbox Code Playgroud)

似乎boolean需要4个字节的存储空间.理想情况下boolean ,只需要一点(falsetrue,01等等).

这里发生了什么?这种boolean类型真的很低效吗?

Han*_*ant 236

布尔类型都有语言运行时之间不兼容的许多选项的曲折的历史.这开始于由发明C语言的人Dennis Ritchie做出的历史设计选择.它没有bool类型,替代方案是int,其中值0表示false,任何其他值都被认为是true.

这个选择在Winapi中继续使用,这是使用pinvoke的主要原因,它有一个typedef,BOOL它是C编译器的int关键字的别名.如果不应用显式[MarshalAs]属性,则将C#bool转换为BOOL,从而生成一个4字节长的字段.

无论您做什么,您的结构声明都需要与您使用的语言进行的运行时选择相匹配.如上所述,对于winapi的BOOL,但大多数C++实现选择了字节,大多数COM自动化互操作使用VARIANT_BOOL这是一个简短的.

C#的实际大小bool是一个字节.CLR强大的设计目标是您无法找到答案.布局是一个实现细节,它依赖于处理器太多.处理器对变量类型和对齐非常挑剔,错误的选择会显着影响性能并导致运行时错误.通过使布局不可发现,.NET可以提供不依赖于实际运行时实现的通用类型系统.

换句话说,您始终必须在运行时封送结构以确定布局.此时,进行从内部布局到互操作布局的转换.如果布局相同,则速度非常快,当需要重新排列字段时,速度很慢,因为总是需要创建结构的副本.这个技术术语是blittable,将blittable结构传递给本机代码很快,因为pinvoke marshaller可以简单地传递一个指针.

性能也是bool不是单一位的核心原因.很少有处理器可以直接寻址,最小的单位是一个字节.需要一个额外的指令来从字节中删除该位,这不是免费的.它绝不是原子的.

C#编译器并不羞于告诉你需要1个字节,请使用sizeof(bool).对于字段在运行时占用的字节数,这仍然不是一个很好的预测器,CLR还需要实现.NET内存模型,它承诺简单的变量更新是原子的.这要求变量在内存中正确对齐,以便处理器可以通过单个内存总线周期更新它.通常,bool实际上需要内存中的4或8个字节.添加了额外的填充以确保下一个成员正确对齐.

CLR实际上利用了不可发现的布局,它可以优化类的布局并重新排列字段,从而最小化填充.所以,比方说,如果你有一个bool + int + bool成员的类,那么它需要1 +(3)+ 4 + 1 +(3)字节的内存,(3)是填充,总共12字节.50%的浪费.自动布局重新排列为1 + 1 +(2)+ 4 = 8个字节.只有一个类具有自动布局,结构默认具有顺序布局.

更惨淡的是,在使用支持AVX指令集的现代C++编译器编译的C++程序中,bool可能需要多达32个字节.这假设了一个32字节的对齐要求,bool变量最终可能会有31个字节的填充..NET抖动不会发出SIMD指令的核心原因,除非明确包装,否则无法获得对齐保证.

  • 哇!一篇文章给出了很多可以理解的主题.这就是我喜欢阅读热门问题的原因. (3认同)
  • 对于感兴趣但不知情的读者,您是否会澄清最后一段是否应该真正读取32*字节*而不是*位*? (2认同)
  • 不知道为什么我只是阅读所有这些(因为我不需要这么多细节),但这很有趣,写得很好. (2认同)
  • @Silly - 它是*字节*.AVX使用512位变量通过一条指令对8个浮点值进行数学运算.这样的512位变量需要与32对齐. (2认同)

Jon*_*eet 148

首先,这只是互操作的大小.它不代表数组托管代码的大小.那是每个1字节bool- 至少在我的机器上.您可以使用以下代码自行测试:

using System;
class Program 
{ 
    static void Main(string[] args) 
    { 
        int size = 10000000;
        object array = null;
        long before = GC.GetTotalMemory(true); 
        array = new bool[size];
        long after = GC.GetTotalMemory(true); 

        double diff = after - before; 

        Console.WriteLine("Per value: " + diff / size);

        // Stop the GC from messing up our measurements 
        GC.KeepAlive(array); 
    } 
}
Run Code Online (Sandbox Code Playgroud)

现在,为了按值编组数组,文档说:

当MarshalAsAttribute.Value属性设置ByValArray为时,必须设置SizeConst字段以指示数组中的元素数.当需要区分字符串类型时,该ArraySubType字段可以选择包含UnmanagedType数组元素.您UnmanagedType只能在元素显示为结构中的字段的数组上使用它.

所以我们看一下ArraySubType,并且有以下文档:

您可以将此参数设置为UnmanagedType枚举中的值,以指定数组元素的类型.如果未指定类型,则使用与托管数组的元素类型对应的缺省非托管类型.

现在看UnmanagedType,有:

Bool
一个4字节的布尔值(true!= 0,false = 0).这是Win32 BOOL类型.

所以这是默认值bool,它是4个字节,因为它对应于Win32 BOOL类型 - 因此,如果您正在与期望BOOL数组的代码进行互操作,那么它就完全符合您的要求.

现在您可以指定ArraySubTypeas,I1而不是记录为:

1字节有符号整数.您可以使用此成员将布尔值转换为1字节的C样式bool(true = 1,false = 0).

因此,如果您要与之互操作的代码每个值需要1个字节,那么只需使用:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;
Run Code Online (Sandbox Code Playgroud)

然后,您的代码将显示每个值占用1个字节,如预期的那样.