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

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (21)

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

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;
}

和下面的代码:

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();
    }
}

这给了我以下输出:

sizeof array of bytes: 3
sizeof array of bools: 12

它似乎是一个boolean需要4个字节的存储空间。理想情况下boolean ,只需要一点(falsetrue01等)。

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

提问于
用户回答回答于

首先,这只是 interop的大小。它不代表数组托管代码中的大小。bool至少在我的机器上是每个1字节。你可以使用此代码自行测试它:

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); 
    } 
}

现在,按照价值对数组进行编组,文档说:

当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风格布尔值(true = 1,false = 0)。

因此,如果你正在进行互操作的代码每个值需要1个字节,请使用:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)]
public bool[] values;

你的代码将显示每个值占用1个字节,如预期的那样。

用户回答回答于

布尔类型都有语言运行时之间不兼容的许多选项的曲折的历史。这是从发明C语言的人Dennis Ritchie的历史设计开始的。它没有bool类型,替代方法是int,其中值为0表示为false且任何其他值均为

这个选择是在Winapi中进行的,这是使用pinvoke的主要原因,它有一个typedef,BOOL它是C编译器的int关键字的别名。如果不应用明确的[MarshalAs]属性,则C#布尔转换为BOOL,从而生成长度为4个字节的字段。

不管你做什么,你的结构声明都需要与用你所使用的语言进行的运行时选择匹配。如前所述,BOOL for winapi,但大多数C ++实现选择字节,大多数COM Automation interop使用VARIANT_BOOL这是一个简短的例子。

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

换句话说,你总是需要在运行时编组结构来确定布局。此时将进行从内部布局到互操作布局的转换。如果布局相同,那么速度可以非常快,当字段需要重新排列时速度很慢,因为这总是需要创建结构的副本。这个技术术语是blittable,因为pinvoke编组可以简单地传递一个指针,所以向本地代码传递一个blittable结构是很快的。

性能也是布尔不是一个单一位的核心原因。有几个处理器可以直接寻址,最小的单元是一个字节。需要一个额外的指令来捕捉字节,这不是免费的。它从来不是原子的。

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个字节。只有一个类具有自动布局,默认情况下,结构具有顺序布局。

更糟糕的是,一个bool可能需要多达32个字节的C ++程序,并且使用支持AVX指令集的现代C ++编译器进行编译。这强加了一个32字节的对齐要求,布尔变量可能会以31个字节的填充结束。此外,.NET抖动不发出SIMD指令的核心原因,除非明确包装,否则无法获得对齐保证。

扫码关注云+社区