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

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

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类型真的这么低效吗?

37719 次浏览

首先,这是互操作的大小 只有。它不表示数组托管代码中的大小。也就是每个 bool1字节-至少在我的机器上是这样。您可以使用下面的代码对其进行测试:

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

现在,对于按值编组数组,像您现在这样,文件说:

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

所以我们来看看 ArraySubType,它有以下文档:

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

现在看看 UnmanagedType,有:

布尔
一个4字节的布尔值(true! = 0,false = 0)。这是 Win32BOOL 类型。

所以这是 bool的默认值,它是4字节,因为它对应于 Win32 BOOL 类型-所以如果您正在与需要 BOOL数组的代码进行交互,那么它正好满足您的需要。

现在可以将 ArraySubType指定为 I1,其文档如下:

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

因此,如果与您交互的代码期望每个值1字节,那么只需使用:

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

然后,您的代码将按照预期的那样,显示为每个值占用1个字节。

布尔类型的历史记录参差不齐,在语言运行时之间有许多不兼容的选择。这始于发明 C 语言的丹尼斯 · 里奇(Dennis Ritchie)所做的一个历史性的设计选择。它没有 布尔类型,可以选择 Int,其中0表示 假的,其他任何值都被认为是 没错

这个选择在 Winapi 继承了下来,这是使用 pcall 的主要原因,它有一个类型定义(typedef)来表示 BOOL,这是 C 编译器的 Int关键字的别名。如果您没有应用显式的[ MarshalAs ]属性,那么 C # 布尔将被转换为 BOOL,从而产生一个长度为4字节的字段。

无论您做什么,您的 struct 声明都需要与您使用的语言所做的运行时选择相匹配。如前所述,对于 winapi 的 BOOL,但是大多数 C + + 实现选择了 字节,大多数 COM 自动化互操作使用的是 VARIANT _ BOOL,它是 太短了

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

换句话说,您总是必须在运行时封送一个结构来确定布局。在此时进行从 内部布局到互操作布局的转换。如果布局相同,那么这个过程会非常快,但是当需要重新排列字段时,速度会很慢,因为这总是需要创建结构的副本。这里的技术术语是 一闪而过,将 blittable 结构传递给本机代码非常快,因为 pcall 编组器可以简单地传递一个指针。

性能也是 布尔不是单个位的核心原因。很少有处理器可以直接寻址,最小的单位是一个字节。需要使用 额外的指令从字节中捕获位,这并不是免费的。而且从来不是原子弹。

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字节的对齐要求,bool 变量最终可能需要31字节的填充。也是为什么一个。NET 抖动不会发出 SIMD 指令,除非显式包装,否则不能保证对齐。

其他的答案显然是正确的,一个 bool 是1字节。 这增加了一个工作示例,表明一个 bool 确实只读写一个字节的内存,不多也不少。

using System;
using System.Runtime.InteropServices;


public class Program
{
[StructLayout(LayoutKind.Explicit)]
struct BoolIntUnion
{
[FieldOffset(0)]
public UInt32 i;


[FieldOffset(0)]
public bool b;
}


public static void Main()
{
var u = new BoolIntUnion();


//first let's see how many bits a boolean reads from memory
//we will do this by reading/writing an Int32 and a boolean to the same place in memory and observe the results


//if a bool is only 8 bits, then only the first 8 bits of a UInt32 will make the boolean become true
u.i = 0b00000000_00000000_00000000_00000001; //try bit 1
if (u.b) Console.WriteLine("True for " + u.i);
u.i = 0b00000000_00000000_00000000_10000000; //try bit 8
if (u.b) Console.WriteLine("True for " + u.i);


//now set all bits on except for the first 8, the boolean should be false if it only accesses the first 8 bits
u.i = 0b11111111_11111111_11111111_00000000;
if (!u.b) Console.WriteLine("False for " + u.i);


//now let's go the other way and see how many bits a boolean writes to memory
u.i = 0b11111111_11111111_11111111_11111111;
u.b = false; //overlay a boolean "false" on top of an UInt32 that has all the bits turned on
if (u.i == 0b11111111_11111111_11111111_00000000) Console.WriteLine("Overlaying a bool on top of a UInt32 cleared only the first 8 bits");
}
}