Общая начальная последовательность и выравнивание
Думая о контр-примере для этого вопроса, я придумал:
struct A
{
alignas(2) char byte;
};
Но если это законный и стандартный макет, совместим ли он с этим макетом? struct B
?
struct B
{
char byte;
};
Кроме того, если у нас есть
struct A
{
alignas(2) char x;
alignas(4) char y;
};
// possible alignment, - is padding
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
// x - - - y - - - x - - - y - - -
struct B
{
char x;
char y;
}; // no padding required
union U
{
A a;
B b;
} u;
Существует ли общая начальная последовательность для A
а также B
? Если так, включает ли это A::y
& B::y
? Т.е. можем ли мы написать следующее без вызова UB?
u.a.y = 42;
std::cout << u.b.y;
(ответы на C++1y / "fixed C++11" также приветствуются)
См. [Basic.align] для выравнивания и [dcl.align] для спецификатора выравнивания.
[basic.types] / 11 говорит о фундаментальных типах "Если два типа
T1
а такжеT2
того же типа, тоT1
а такжеT2
являются совместимыми с макетом типами." (основной вопрос заключается в том,A::byte
а такжеB::byte
иметь совместимые с макетом типы)[class.mem] / 16 "Два типа структуры стандартного макета совместимы с макетом, если они имеют одинаковое количество элементов не статических данных, а соответствующие элементы нестатических данных (в порядке объявления) имеют типы, совместимые с макетом".
[class.mem]/18 "Две стандартные структуры компоновки имеют общую начальную последовательность, если соответствующие элементы имеют типы, совместимые с компоновкой, и ни один из них не является битовым полем, либо оба являются битовыми полями одинаковой ширины для последовательности из одного или более начальных членов. "
[class.mem]/18 "Если объединение стандартной компоновки содержит две или более структур стандартной компоновки, которые имеют общую начальную последовательность, и если объект объединения стандартной компоновки в настоящее время содержит одну из этих структур стандартной компоновки, это разрешается проверить общую начальную часть любого из них. "
Разумеется, на уровне юристов по языку другой вопрос заключается в том, что означает, что проверка общей начальной последовательности "разрешена". Я думаю, какой-то другой абзац может сделать u.b.x
неопределенное поведение (чтение из неинициализированного объекта).
3 ответа
Похоже на дыру в стандарте. Ответственным было бы подать отчет о дефекте.
Несколько вещей, хотя:
- Ваш первый пример на самом деле не демонстрирует проблему. Добавление
short
послеchar
также будет иметь эффект выравниванияchar
до 2-байтовой границы, без изменения общей подпоследовательности. alignas
не только C++; он был добавлен одновременно к C11. Поскольку свойство стандартного макета является средством межязыковой совместимости, вероятно, предпочтительнее требовать соответствия соответствующих спецификаторов выравнивания, чем дисквалифицировать класс с нестатическим спецификатором выравнивания члена.- Не было бы никаких проблем, если бы спецификаторы выравнивания элементов применялись к типам элементов. Другие проблемы могут возникать из-за отсутствия настройки типов, например, параметра функции
ret fn( alignas(4) char )
может потребоваться, чтобы ABI корректно обработал его, но язык может не предусматривать такую корректировку.
Я не могу говорить о стандарте C++11, но я программист микропрограммного обеспечения и микропроцессоров и должен использовать такие функции, которые существуют в течение длительного времени (пакет прагм, атрибуты выравнивания).
С помощью alignas
не может рассматриваться как "стандартная схема", поэтому все последствия бесполезны. Стандартная компоновка означает одно распределение с фиксированным выравниванием (для каждой архитектуры - обычно все align(min(sizeof,4))
или некоторые могут быть align(8)
). Стандарт, вероятно, хочет сказать, что очевидно: без использования специальных функций (align
, pack
) структуры совместимы на одной архитектуре, если они выглядят одинаково (одинаковые типы в одинаковом порядке). В противном случае они могут и не могут быть совместимы - в зависимости от архитектуры (могут быть совместимы на одной архитектуре, но могут отличаться на другой).
Рассмотрим эту структуру:
struct foo{ char b; short h; double d; int i; };
На одной архитектуре (например, x86 32bit) это то, что кажется, но на Itanium или ARM это выглядит так:
struct foo{char b, **_hidden_b**; short h; **int _maybe_hidden_h**; double d; int i;}
уведомление _maybe_hidden_h
- это может быть опущено в более старом AEABI (выравнивание до 4) или там для выравнивания 64 бит /8B.
Стандартное расположение x86 (упаковка (1)):
alignas(1) char b; alignas(1) short h; alignas(1) double d; alignas(1) int i;
Стандартное расположение 32-битного выравнивания (пакет (4) - архитектура ARM, более старая версия - EABI)
alignas(1) char b; alignas(2) short h; **alignas(4) double d**; alignas(4) int i;
Стандартная компоновка для 64-битного выравнивания (пакет (8) - Itanium и более новые ARM/AEABI)
alignas(1) char b; alignas(2) short h; **alignas(8) double d**; alignas(4) int i;
К вашему примеру:
offsetof(A,y) = 4
в то время как offsetof(B,y) = 2
и союз не меняет это (таким образом, &u.a.y != u.b.y
)
(основной вопрос заключается в том, имеют ли A::byte и B::byte совместимые с макетом типы)
Да. Это важная часть. alignas
атрибут относится к объявленной сущности, а не к типу. Может быть легко проверено std::is_same
а также decltype
,
Т.е. можем ли мы написать следующее без вызова UB?
Поэтому это не UB, соответствующие параграфы были указаны вами.
РЕДАКТИРОВАТЬ: Извините, это, конечно, может привести к UB, потому что заполнение между членами не (или реализации-) не определен (§9.2/13)! Я случайно неправильно прочитал пример, потому что я думал, что он получил доступ к x вместо y, потому что с x это на самом деле всегда работает - тогда как с y это теоретически не должно (хотя это практически всегда будет).