Несоответствие в C ++ между выровненной структурой и перечислением внутри контейнера - PullRequest
8 голосов
/ 25 июня 2019

В C ++, по крайней мере, в GCC и Clang, слишком выровненный тип, встроенный в контейнер (std :: vector), по-видимому, обрабатывается по-разному в зависимости от того, является ли тип переопределенной структурой или переопределенным перечислением , Для версии struct, элемент выровнен для каждого, в то время как для перечисляемой только общий буфер имеет указанное выравнивание. Это поведение определено стандартом? И если да, то в какой части это упоминается? Или определяется реализацией и на нее не следует полагаться?

Обратите внимание на следующее:

#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-байтовой границе, в отличие от байта_enum, для которого выравнивание применяется только к буферу в целом, но не для каждого отдельного элемента.

Это поведение идентично в GCC 9.1 и Clang 8.0, в то время как MSVC 19.20 обнаруживает внутреннюю ошибку компилятора.

Ссылка для проводника компилятора: https://godbolt.org/z/GUg2ft

Ответы [ 3 ]

6 голосов
/ 26 июня 2019

Это проблема основной рабочей группы C ++ 2354 , которая недавно была решена путем удаления разрешения на применение alignas к типу enum.(На момент написания статьи последняя общедоступная версия списка проблем не содержала резолюцию, но вы можете найти решение в P1359R0 , который был принят в рабочий проект C ++ в феврале 2019 года и приняткак отчет о дефектах (что означает, что исправление предназначено для применения задним числом).

Проблема в том, что у нас было два противоречивых требования:

  1. и enum-base (включая неявное enum-base из int в перечислении с областью действия) указывает базовый тип перечисления, и перечисление должно иметь такое же представление объекта (включая sizeofи представление всех значений) в качестве базового типа, а

  2. и спецификатор выравнивания указывает выравнивание типа, которое, в свою очередь, также должно ограничивать sizeof(E)(которое по определению является расстоянием между двумя объектами типа E в массиве) до кратного указанного выравнивания.

Вы не можетеи то и другое, поэтому мы разрешили конфликт, удалив возможность указать выравнивание для типа перечисления.

Лучший совет - не применять спецификатор выравнивания к типу перечисления;реализации перестанут принимать это в какой-то момент.(Применяя выравнивание к , используйте типа в объявлении переменной или нестатического члена данных, все в порядке.)

2 голосов
/ 25 июня 2019

Да, это похоже на ошибку.

В соответствии со стандартом - 9.11.2:

1 Спецификатор выравнивания может применяться к переменной или кчлен данных класса, но он не должен применяться к битовому полю, параметру функции или объявлению исключения (13.3).Спецификатор выравнивания может также применяться к объявлению класса (в подробном спецификаторе типа (9.1.7.3) или заголовке класса (раздел 10), соответственно) и к объявлению перечисления (в непрозрачномenum-декларация или enum-head соответственно (9.6)).Спецификатор выравнивания с многоточием - это расширение пакета (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;
        }
    }
1 голос
/ 26 июня 2019

Это не ответ, но дано:

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

ленточные отпечатки:

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;
                                                      ^
...