C++ 中容器内过度对齐的结构和枚举之间的差异

Discrepancy in C++ between over-aligned struct and enum within container

在 C++ 中,至少在 GCC 和 Clang 上,嵌入在容器中的过度对齐类型 (std::vector) 似乎会根据类型是过度对齐结构还是过度对齐而受到不同对待对齐的枚举。对于结构版本,每个元素都对齐,而对于枚举版本,只有整个缓冲区具有指定的对齐方式。 标准是否指定了此行为?如果是这样,哪一部分提到了它?还是实现定义的,不应依赖?

考虑以下因素:

#include<cstdint>
#include<iostream>
#include<vector>

struct alignas(16) byte_struct {std::uint8_t value;};
enum alignas(16) byte_enum : std::uint8_t {};

int main() {
    {//with struct
        std::vector<byte_struct> bytes;
        bytes.push_back(byte_struct{1});
        bytes.push_back(byte_struct{2});
        bytes.push_back(byte_struct{3});
        for(auto it = bytes.begin(); it!= bytes.end(); ++it) {
                std::cout<<&*it<<std::endl;
        }
    }
    {//with enum
        std::vector<byte_enum> bytes;
        bytes.push_back(byte_enum{1});
        bytes.push_back(byte_enum{2});
        bytes.push_back(byte_enum{3});
        for(auto it = bytes.begin(); it!= bytes.end(); ++it) {
                std::cout<<&*it<<std::endl;
        }
    }
}

结构过度对齐的版本打印如下

0x10a9ec0 0x10a9ed0 0x10a9ee0

具有过度对齐枚举的版本打印以下内容

0x10a9e70 0x10a9e71 0x10a9e72

在向量存储中,每个 byte_struct 都对齐到 16 字节边界,而 byte_enum 则对齐仅适用于整个缓冲区而不适用于每个单独的元素.

此行为在 GCC 9.1 和 Clang 8.0 上是相同的,而 MSVC 19.20 遇到内部编译器错误。

编译器资源管理器的 link 是:https://godbolt.org/z/GUg2ft

是的,这似乎是一个错误。

根据标准 - 9.11.2:

1 An alignment-specifier may be applied to a variable or to a class data member, but it shall not be applied to a bit-field, a function parameter, or an exception-declaration (13.3). An alignment-specifier may also be applied to the declaration of a class (in an elaborated-type-specifier (9.1.7.3) or class-head (Clause 10), respectively) and to the declaration of an enumeration (in an opaque-enum-declaration or enum-head, respectively (9.6)). An alignment-specifier with an ellipsis is a pack expansion (12.6.3).

枚举也应该对齐。

UBSan 也抱怨:

/usr/include/c++/8.3.0/ext/new_allocator.h:136: runtime error: store to misaligned address 0x602000000031 for type 'byte_enum', which requires 16 byte alignment
0x602000000031: note: pointer points here
 00 80 58  be be 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00
              ^ 
/usr/include/c++/8.3.0/bits/stl_iterator.h:797:17: runtime error: reference binding to misaligned address 0x602000000031 for type 'byte_enum', which requires 16 byte alignment
0x602000000031: note: pointer points here
 00 80 58  01 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00
              ^ 
/usr/include/c++/8.3.0/bits/stl_vector.h:1033:20: runtime error: reference binding to misaligned address 0x602000000031 for type 'value_type', which requires 16 byte alignment
0x602000000031: note: pointer points here
 00 80 58  01 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00
              ^ 
0x602000000050
0x602000000051
0x602000000052

这可能是一个 libstdc++ std::vector 错误,因为使用数组在 UBSan 中运行良好:

    {//with enum
        std::array<byte_enum, 3> bytes = { byte_enum{1}, byte_enum{2}, byte_enum{3} };
        for(auto it = bytes.begin(); it!= bytes.end(); ++it) {
                std::cout<<&*it<<std::endl;
        }
    }

这不是答案,而是给出:

std::cout << "16 structs: " << sizeof(byte_struct[16]) << std::endl;
std::cout << "16 enums:   " << sizeof(byte_enum  [16]) << std::endl;

clang 打印:

16 structs: 256
16 enums:   16

gcc 报错:

error: alignment of array elements is greater than element size
  std::cout << "16 enums:   " << sizeof(byte_enum  [16]) << std::endl;
                                                      ^

这是 C++ core working group issue 2354, which was recently resolved by removing the permission to apply alignas to an enum type. (At the time of writing, the latest public version of the issues list doesn't contain the resolution, but you can find the resolution in P1359R0,它于 2019 年 2 月被采纳到 C++ 工作草案中,并被接受为缺陷报告(这意味着该修复程序旨在追溯应用)。

问题是我们有两个相互冲突的要求:

  1. an enum-base(包括[=12=的隐式enum-base ] in a scoped enumeration)指定枚举的底层类型,要求枚举与其底层类型具有相同的对象表示(包括sizeof和所有值的表示),并且

  2. an alignment-specifier 指定类型的对齐方式,这反过来也必须约束 sizeof(E)(根据定义数组中 E 类型的两个对象之间的距离)到所述对齐的倍数。

你不能同时拥有两者,因此我们通过删除在枚举类型上指定对齐方式的功能来解决冲突。

最好的建议是不要将对齐说明符应用于枚举类型;实现将在某个时候停止接受它。 (不过,将对齐应用于变量声明中类型的 use 或 non-static 数据成员是可以的。)