13
votes

While thinking of a counter-example for this question, I came up with:

struct A
{
    alignas(2) char byte;
};

But if that's legal and standard-layout, is it layout-compatible to this struct B?

struct B
{
    char byte;
};

Furthermore, if we have

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;

Is there a common initial sequence for A and B? If so, does it include A::y & B::y? I.e., may we write the following w/o invoking UB?

u.a.y = 42;
std::cout << u.b.y;

(answers for C++1y / "fixed C++11" also welcome)


  • See [basic.align] for alignment and [dcl.align] for the alignment-specifier.

  • [basic.types]/11 says for fundamental types "If two types T1 and T2 are the same type, then T1 and T2 are layout-compatible types." (an underlying question is whether A::byte and B::byte have layout-compatible types)

  • [class.mem]/16 "Two standard-layout struct types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout-compatible types."

  • [class.mem]/18 "Two standard-layout structs share a common initial sequence if corresponding members have layout-compatible types and either neither member is a bit-field or both are bit-fields with the same width for a sequence of one or more initial members."

  • [class.mem]/18 "If a standard-layout union contains two or more standard-layout structs that share a common initial sequence, and if the standard-layout union object currently contains one of these standard-layout structs, it is permitted to inspect the common initial part of any of them."

Of course, on a language-lawyer level, another question is what it means that the inspection of the common initial sequence is "permitted". I guess some other paragraph might make the above u.b.x undefined behaviour (reading from an uninitialized object).

3
I don't think this is a good example. The structure with an int and a char has int alignment. That alignas(2) attribute for char byte as a first element is a no-op because that first element already has alignas(int) alignment. A possibly better example: struct A {int x; alignas(double) char byte;};David Hammen
@DavidHammen Ouch, true, I've meant to add padding after the byte. Fixing..dyp
@DavidHammen I hope the example is better now.dyp
Side note: If a class using alignas on its members is not intended to be standard-layout, then sizeof(A) could be four, with the second member at offset 0, and the first at offset 2. Somewhat more relevant note: the current wording of "standard-layout" that already makes the literal requirements unimplementable for other reasons. Details here. I looked for open issues regarding alignment too, but found nothing of interest.user743382
@Tshepang According to the tag wiki, [union] is for SQL UNION, whereas [unions] is for C, C++ etc. unions.dyp

3 Answers

2
votes

It looks like a hole in the standard. The responsible thing would be to file a defect report.

Several things, though:

  • Your first example doesn't really demonstrate a problem. Adding a short after the char would also have the effect of aligning the char to a 2-byte boundary, without changing the common subsequence.
  • alignas is not C++-only; it was added simultaneously to C11. Since the standard-layout property is a cross-language compatibility facility, it is probably preferable to require corresponding alignment specifiers to match than to disqualify a class with a nonstatic member alignment-specifier.
  • There would be no problem if the member alignment specifiers appertained to the types of the members. Other problems may result from the lack of adjustment to types, for example a function parameter ret fn( alignas(4) char ) may need to be mangled for the ABI to process it correctly, but the language might not provide for such adjustment.
2
votes

I may not speak for C++11 standard, but am a firmware/microchip programmer and have to use such features that exist for a long time (pragma pack, alignment attributes).

Using alignas cannot be considered "standard layout", thus all the implications are useless. Standard layout means one fixed alignment distribution (per architecture - usually all is align(min(sizeof,4)) or some may be align(8)). The standard probably want's to say what is obvious: without using special features (align,pack) structures are compatible on same architecture if they apepar to be the same (same types in same order). Otherwise, they may and may not be compatible - depending on architecture (may be compatible on one architecture but different on another).

Consider this struct:

struct foo{ char b; short h; double d; int i; };

On one architecture (e.g. x86 32bit) it is what it seems to be, but on Itanium or ARM it actually looks like this:

struct foo{char b, **_hidden_b**; short h; **int _maybe_hidden_h**; double d; int i;}  

Notice _maybe_hidden_h - it can be omitted in older AEABI (align to max 4) or there for 64bit/8B alignment.

x86 Standard Layout (pack(1)):

alignas(1) char b; alignas(1) short h; alignas(1) double d; alignas(1) int i;  

32bit Alignment Standard Layout (pack(4) - ARM architecture, older version - EABI)

alignas(1) char b; alignas(2) short h; **alignas(4) double d**; alignas(4) int i;  

64bit Alignment Standard Layout (pack(8) - Itanium and newer ARM/AEABI)

alignas(1) char b; alignas(2) short h; **alignas(8) double d**; alignas(4) int i;

To your example:
offsetof(A,y) = 4 while offsetof(B,y) = 2 and the union does not change that (thus &u.a.y != u.b.y)

0
votes

(an underlying question is whether A::byte and B::byte have layout-compatible types)

Yes. This is the essential part. The alignas-attribute appertains to the entity declared, not the type. Can be easily tested by std::is_same and decltype.

I.e., may we write the following w/o invoking UB?

This is therefore not UB, the relevant paragraphes have been quoted by you.

EDIT: Pardon me, this can of course result in UB because the padding between members is not (or implementation-) defined (§9.2/13)! I accidently misread the example, because i thought it accessed x instead of y, because with x it actually always works - whereas with y it theoretically doesn't have to (though it practically always will).