我经常在与数据类型相关的场合中找到“比特填充”的术语,但我不理解它是什么,以及它对这些数据类型的确切作用。
其本质是它们是“浪费”空间。我说“浪费”,因为虽然填充位使对象变大,但可以使使用对象变得更加容易(这意味着更快),而小的空间浪费可以产生巨大的性能提升。在某些情况下,它是必不可少的,因为CPU无法处理那么大的对象。
假设您有一个结构体如下:
struct foo
{
short a; // 16 bits
char b; // 8 bits
};
而您正在使用的机器可以在单个读取操作中读取32位数据。 读取单个foo并不是问题,因为整个对象适合于那个32位块中。 当您有一个数组时,会出现问题。 关于数组需要记住的重要事情是它们是连续的,元素之间没有空间。 这只是一个紧随其后的另一个对象。 因此,如果您有像以下这样的数组
foo array[10]{};
使用这种方法,第一个foo
对象位于一个32位桶中。但是,数组的下一个元素将位于第一个32位桶和第二个32位桶中。这意味着成员a
位于两个不同的桶中。一些处理器可以这样做(但需要代价),而其他处理器会在尝试执行此操作时崩溃。为了解决这两个问题,编译器会向foo
添加填充位来填充其大小。这意味着实际上foo
变成了
struct foo
{
short a; // 16 bits
char b; // 8 bits
char _; // 8 bits of padding
};
现在处理器可以很容易地单独或者在数组中处理foo
对象。它不需要做任何额外的工作,你只增加了每个对象8位。在现代计算机上,您需要大量的对象才能开始关注这一点。
有时候您需要填充类型成员之间的空白以进行未对齐访问。比方说您有:
struct bar
{
char c; // 8 bits
int d; // 32 bits
};
现在,
bar
的宽度为40位,并且
d
往往会被再次存储在两个不同的桶中。为了解决这个问题,编译器在
c
和
d
之间添加填充位,例如:
struct bar
{
char c; // 8 bits
char _[3]; // 24 bits
int d; // 32 bits
};
现在,d
被保证会进入一个 32 位的桶中。