购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.5 控制以及查询对象对齐方式

C++11标准提供了用于指定或获取一种类型的对齐方式(在此之前只能依赖编译器特有的办法 )的方法。控制对齐方式对于提高不同处理器的性能并启用某些仅适用于特定对齐方式的数据的指令非常重要。例如,对于Intel SSE(Streaming SIMD Extensions)和Intel SSE2的处理器,如果按16字节对齐,它们处理相同的数据的速度会得到大幅提升。另外,对于Intel AVX(Intel Advanced Vector Extensions),它将大多数整数处理器的命令扩展到256位,强烈建议使用32字节对齐。本节探讨如何用alignas标识符来控制字节对齐方式,以及如何用alignof操作符来检查类型字节对齐要求。

1.5.1 准备工作

你应该熟悉数据对齐方式以及编译器执行默认数据对齐的方式。关于后者的具体知识我们会放到1.5.3节介绍。

1.5.2 使用方式

❍ 控制数据类型或对象的对齐方式(既包含类层面的,也包含数据成员层面的),用alignas标识符:

❍ 获取数据类型的对齐方式,用alignof操作符:

1.5.3 工作原理

处理器不会一次只访问一个字节,一般会访问一块比较大的区域,这块区域的大小一般为2的整数幂(2、4、8、16、32等)。基于此,为了提高处理器的处理速度,编译器对于内存的数据对齐就显得尤为重要。如果数据未对齐,编译器必须做额外的工作来访问这些未对齐的数据,例如,它必须读取成倍的数据块,然后裁剪并丢弃不需要的字节,最后将它们组合在一起。

C++编译器是根据数据类型来对齐变量的。该标准仅指定char、signed char、unsigned char、char8_t和std::byte的大小必须为1。它还规定short的大小至少为16位,long的大小至少为32位,long long的大小至少为64位。同时也规定1==sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long)<=sizeof(long long)。因此,大多数类型的大小是由编译器指定的且依赖于平台。比较典型的是,bool和char类型的大小为1字节,short为2字节,int、long和float为4字节,double和long long为8字节,等等。当涉及结构或联合体时,对齐方式必须与最大成员的大小匹配,以避免出现性能问题。为了说明这一点,我们来探究一下下面的数据结构:

foo1和foo2的大小不一样,但是对齐方式是一样的,(即都按1字节对齐),因为所有的成员都是1字节char类型的。foo3的第二个成员是整型,它的大小是4字节。因此,这个结构的成员对齐的地址是4的整数倍。为了达到这个要求,编译器会填充一些字节。

结构foo3实际上被转化成了下面这样:

类似地,下面这个结构的大小是32字节,并且按8字节对齐,这是因为它有一个大小为8字节的double数据成员。因此,这个结构需要填充更多的字节,以确保访问的地址为8的倍数:

编译器生成的等价结构如下:

在C++11中,可以使用alignas标识符来指定对象或类型的对齐方式。这可以采用表达式(计算结果为0的整数常量表达式或对齐的有效值)、type-id或者参数包来表示。alignas标识符可应用于不表示位字段的变量或类数据成员的声明,也可应用于类、联合体或枚举结构的声明。

用alignas标识符声明的类型或变量的对齐字节数等于alignas表达式中的最大值(大于0)。

使用alignas标识符有一些限制:

❍ 对齐字节数必须是2的幂(2、4、8、16、32……)。其他的值则是非法的,而且程序看起来也是不规范的,对于这种情况,编译器没有必要给出错误,它们会直接忽略该标识符。

❍ 按0字节对齐的方式总是会被忽略。

❍ 如果alignas标识符声明的对齐字节数比默认(没有用alignas标识符)的对齐字节数小,则该程序也被认为是不规范的。

在下面的例子中,alignas标识符被用于类声明。没有用alignas标识符声明的默认对齐字节数是1,但是当使用alignas(4)时,对齐字节数变成了4:

换句话说,编译器将前面的类翻译成了下面这个:

alignas标识符既可以用于类声明,也可以用于成员数据声明。在这种情况下,采取最“严格”的原则来确定对齐的字节数。在下面的例子中,成员a的默认大小为1字节,但是被要求按2字节对齐,成员b的默认大小为4字节,但是被要求按8字节对齐,所以最严格的对齐字节数是8。整个类要求按4字节对齐,但是比最严格的8字节对齐要宽松,所以会被忽略,但是编译器会给出一个警告:

上述结构等同于:

alignas标识符也可以用来修饰变量。在下面的例子中,整数变量a被要求放在内存地址为8的整数倍的地方。变量b(代表4个long类型元素的数组)被要求放在内存地址为256的整数倍的地方。因此,编译器会在两个变量之间填充244字节(取决于变量a在内存的位置,即内存地址为8的倍数的位置):

看下输出的内存地址,我们可以看到变量a的内存地址确实是8的整数倍,且b的内存地址也确实是256(16进制表示是0x100)的整数倍。

如果想查看类型的对齐字节数,可以使用alignof操作符。它不同于sizeof,sizeof操作符只适用于type-id,却不适用于变量或者类成员。它适用的类型可以是完全类型、数组类型或引用类型。对于数组而言,对齐字节数为每个元素的对齐字节数;对于引用而言,对齐字节数为其引用的对齐字节数。表1.1给出了一些示例。

表1.1

如果想强制数据对齐(考虑到前面提到的限制)以便数据访问和复制比较高效,alignas标识符则非常有用。这意味着CPU可以避免读写缓存行失效。这在性能关键型应用中显得尤为重要,比如游戏和交易应用。另外,alignof操作符返回指定类型的最小对齐要求。

1.5.4 延伸阅读

❍ 阅读1.2节,以了解类型的别名。 7xbhy+lTiEjqgQJLoK2r60J37ZfYDjR21y1aR0NYJSWdyjmC4VxAwzNhdOMPvgTZ

点击中间区域
呼出菜单
上一章
目录
下一章
×