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

3

8

In C++, on at least GCC and Clang, an over-aligned type embedded within a container (std::vector) seems to be treated differently depending on whether the type is an over-aligned struct or an over-aligned enum. For the struct version, the element is aligned for each, while for the enum one, only the overall buffer is with the specified alignment. Is this behavior specified by the standard? And if so, which part does mention it? Or implementation-defined and should not be relied upon?

Consider the following:

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

The version with the over-aligned struct prints the following

0x10a9ec0 0x10a9ed0 0x10a9ee0

The version with the over-aligned enum prints the following

0x10a9e70 0x10a9e71 0x10a9e72

Within the vector storage, each byte_struct is aligned to 16 bytes boundary, as opposed to the byte_enum for which the alignment applies only to the buffer as a whole but not for each individual element.

This behavior is identical on GCC 9.1 and Clang 8.0, while MSVC 19.20 encounters an internal compiler error.

The link for compiler explorer is: https://godbolt.org/z/GUg2ft

Norty answered 25/6, 2019 at 19:32 Comment(2)
cppreference seems pretty clear that byte_enum should be aligned: "...it can be applied to the declaration or definition of a class/struct/union or enumeration." Just OOC, does it help matters if you actually define (at least) four enumerated identifiers? Casting to an enum type beyond the range of the bit field necessary to represent the underlying enumeration is undefined behavior in C++17, so the compilers are allowed to break out the nasal demons without those definitions.Pestilent
I am not sure exactly what you mean for the second part of your comment. Do you refer to byte_enum{1}? If so, aggregate initialization of enum with specified underlying type by a constant expression should be fine. Otherwise, it throws a narrowing warning/error.Norty
W
6

This is 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, which was adopted into the C++ working draft in February 2019 and accepted as a Defect Report (which means the fix is intended to apply retroactively).

The problem is that we had two conflicting requirements:

  1. an enum-base (including the implicit enum-base of int in a scoped enumeration) specifies the underlying type of the enumeration, and the enumeration is required to have the same object representation (including sizeof and representation of all values) as its underlying type, and

  2. an alignment-specifier specifies the alignment of the type, which in turn must also constrain sizeof(E) (which is by definition the distance between two objects of type E in an array) to a multiple of said alignment.

You can't have both, so we resolved the conflict by removing the ability to specify an alignment on an enumeration type.

The best advice is to not apply an alignment specifier to an enumeration type; implementations will stop accepting that at some point. (Applying the alignment to a use of the type in the declaration of a variable or non-static data member is OK, though.)

Waxman answered 25/6, 2019 at 22:35 Comment(0)
S
2

Yes, this seems to be a bug.

According to the standard - 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).

enums should be aligned as well.

UBSan also complains:

/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

That might be a libstdc++ std::vector bug though because using an array runs fine with 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;
        }
    }
Serrated answered 25/6, 2019 at 20:0 Comment(1)
UBSAN also complains if you specify -stdlib=libc++ with clang.Tyburn
T
1

This is not an answer, but given:

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

clang prints:

16 structs: 256
16 enums:   16

and gcc tosses an error:

error: alignment of array elements is greater than element size
  std::cout << "16 enums:   " << sizeof(byte_enum  [16]) << std::endl;
                                                      ^
Tyburn answered 25/6, 2019 at 21:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.