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


137

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

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

似乎a boolean需要4个字节的存储空间。理想情况下,a boolean 只需占用一位(falsetrue01,等等)。

这是怎么回事 是boolean类型真的如此低效?


7
这是在进行中的搁置原因之争中最具有讽刺意味的冲突之一:尽管约翰·汉斯(John and Hans)做出了两个出色的回答,尽管对这个问题的回答几乎完全基于观点,而不是事实,参考,或特定的专业知识。
TaW 2015年

12
@TaW:我的猜测是,亲密的投票不是由于回答,而是因为OP最初提出问题时的原始语气-他们明确打算开始战斗,并在现在删除的评论中直接表明了这一点。大部分文件都被清除了,但请查看修订历史以了解我的意思。
BoltClock

1
为什么不使用BitArray?
ded'15年

Answers:


242

布尔类型都有语言运行时之间不兼容的许多选项的曲折的历史。首先是由发明C语言的人Dennis Ritchie做出的历史性设计选择。它没有布尔类型,替代方法是int,其中值0表示false,而其他任何值都视为true

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

无论做什么,您的struct声明都必须与使用互操作语言选择的运行时匹配。如前所述,BOOL用于winapi,但大多数C ++实现选择字节,大多数COM自动化互操作使用VARIANT_BOOL,它是short

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

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

性能也是布尔值不高的核心原因。使位可直接寻址的处理器很少,最小的单位是字节。需要一条额外的指令才能将位从字节中取出,这不是免费的。而且它从来都不是原子的。

否则,C#编译器不会羞于告诉您它占用1个字节,请使用sizeof(bool)。对于字段在运行时需要占用多少字节,这仍然不是理想的预测器,CLR还需要实现.NET内存模型,并且它保证简单的变量更新是原子的。这要求变量在内存中正确对齐,以便处理器可以用单个内存总线周期对其进行更新。因此,布尔实际上通常需要4或8个字节的内存。添加了额外的填充,以确保下一个成员正确对齐。

CLR实际上利用了不可发现的布局,它可以优化类的布局并重新安排字段,从而使填充最小化。因此,如果您有一个包含bool + int + bool成员的类,则将需要1 +(3)+ 4 + 1 +(3)字节的内存,(3)是填充字节,总共12个字节。50%的浪费。自动布局会重新排列为1 +1 +(2)+ 4 = 8个字节。只有一个类具有自动布局,默认情况下结构具有顺序布局。

更令人沮丧的是,在使用支持AVX指令集的现代C ++编译器编译的C ++程序中,布尔可能需要多达32个字节。这就要求32字节的对齐要求,布尔变量可能以31字节的填充结尾。这也是.NET抖动不发出SIMD指令的核心原因,除非明确包装,否则无法获得对齐保证。



2
对于感兴趣但不了解情况的读者,您能否澄清最后一段是否应该真正读取32 字节而不是
2015年

3
不知道为什么我只读了所有这些内容(因为我不需要那么多的细节),但这很有趣且写得很好。
Frank V

2
@Silly-它是字节。AVX使用512位变量通过一条指令对8个浮点值进行数学运算。这样的512位的变量需要对准〜32
汉斯帕桑特

3
哇!一篇帖子给人以很多话题要理解。这就是为什么我只喜欢阅读最常见的问题。
Chaitanya Gadkari

151

首先,这只是互操作的大小。它不代表数组托管代码的大小。那是每个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); 
    } 
}

现在,对于按值编组数组,文档说:

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

因此,我们看一下ArraySubType,其中包含以下文档:

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

现在看UnmanagedType,有:

Bool
一个4字节的布尔值(真= 0,假= 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个字节。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.