Alignment of a union less than its members?

102 views
Skip to first unread message

Myriachan

unread,
Sep 27, 2017, 4:28:53 PM9/27/17
to ISO C++ Standard - Discussion
For a union U with a non-static member of type T, is it guaranteed by the Standard that alignof(U) >= alignof(T)?

I'm wondering because it's not true for x86-32 Linux and Mac OS, where this asserts:

union Counter
{
   
unsigned long long meow;
};

static_assert(alignof(Counter) >= alignof(unsigned long long), "huh?");

Melissa

Columbo

unread,
Sep 27, 2017, 6:09:30 PM9/27/17
to ISO C++ Standard - Discussion
AFAICS, it isn't stipulated for any kind of class that the alignment is somehow related to that of its subobjects. Perhaps that inequality is necessarily true, though: If you were to create an instance of Counter with the active member meow, that object's storage must have an alignment pertaining to its type (if the implementation respects alignment). If the alignment given by alignof was less than that, I could unawares create a union object in unsuitably aligned memory, precluding a correct alignment of the subobject without violating any constraint. 

Larry Evans

unread,
Sep 27, 2017, 6:40:38 PM9/27/17
to std-dis...@isocpp.org
On 09/27/2017 05:09 PM, Columbo wrote:
> AFAICS, it isn't stipulated for any kind of class that the alignment is
> somehow related to that of its subobjects. Perhaps that inequality is
> necessarily true, though: If you were to create an instance of Counter with
> the active member meow, that object's storage must have an alignment
> pertaining to its type (if the implementation respects alignment). If the
> alignment given by alignof was less than that, I could unawares create a
> union object in unsuitably aligned memory, precluding a correct alignment
> of the subobject without violating any constraint.

To summarize:
alignof(memberI) >= alignof(union_type)

for each member type, memberI, in the
containing union of type union_type.

IOW, th OP static_assert should *not* have fired; hence, I'm very
surprised it did!

Larry Evans

unread,
Sep 27, 2017, 6:55:01 PM9/27/17
to std-dis...@isocpp.org
On 09/27/2017 05:40 PM, Larry Evans wrote:
> On 09/27/2017 05:09 PM, Columbo wrote:
>> AFAICS, it isn't stipulated for any kind of class that the alignment is
>> somehow related to that of its subobjects. Perhaps that inequality is
>> necessarily true, though: If you were to create an instance of Counter
>> with
>> the active member meow, that object's storage must have an alignment
>> pertaining to its type (if the implementation respects alignment). If the
>> alignment given by alignof was less than that, I could unawares create a
>> union object in unsuitably aligned memory, precluding a correct alignment
>> of the subobject without violating any constraint.
>
> To summarize:
>   alignof(memberI) >= alignof(union_type)
>
> for each member type, memberI, in the
> containing union of type union_type.
>
> IOW, th OP static_assert should *not* have fired; hence, I'm very
> surprised it did!
>
[snip]
Actually, on my linux machine using clang3.8, it doesn't fire:

make -k MAIN=union srco
cat union.cpp
//OriginalSource:
// Post to std-dis...@isocpp.org:
/*
https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/jTn0uVOda9k
*/
//=======================

union Counter
{
unsigned long long meow;
};

static_assert(alignof(Counter) >= alignof(unsigned long long), "huh?");
/usr/bin/clang++-3.8 -c -O0 -gdwarf-2 -stdlib=libc++ -std=c++1z
-ftemplate-backtrace-limit=0 -fdiagnostics-show-template-tree
-fno-elide-type -fmacro-backtrace-limit=0 -c union.cpp

Compilation finished at Wed Sep 27 17:54:01


Thiago Macieira

unread,
Sep 27, 2017, 7:24:44 PM9/27/17
to std-dis...@isocpp.org
On quarta-feira, 27 de setembro de 2017 13:28:53 PDT Myriachan wrote:
> For a union U with a non-static member of type T, is it guaranteed by the
> Standard that alignof(U) >= alignof(T)?

No.

> I'm wondering because it's not true for x86-32 Linux and Mac OS, where this
> asserts:
>
> union Counter
> {
> unsigned long long meow;
> };
>
> static_assert(alignof(Counter) >= alignof(unsigned long long), "huh?");

Double and long double have the same problem.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Thiago Macieira

unread,
Sep 27, 2017, 7:25:45 PM9/27/17
to std-dis...@isocpp.org
On quarta-feira, 27 de setembro de 2017 15:54:44 PDT Larry Evans wrote:
> /usr/bin/clang++-3.8 -c -O0 -gdwarf-2 -stdlib=libc++ -std=c++1z
> -ftemplate-backtrace-limit=0 -fdiagnostics-show-template-tree
> -fno-elide-type -fmacro-backtrace-limit=0 -c union.cpp

You forgot -m32.

Thiago Macieira

unread,
Sep 27, 2017, 7:35:26 PM9/27/17
to std-dis...@isocpp.org
On quarta-feira, 27 de setembro de 2017 16:24:37 PDT Thiago Macieira wrote:
> > For a union U with a non-static member of type T, is it guaranteed by the
> > Standard that alignof(U) >= alignof(T)?
>
> No.

Here's the thing: alignof(T) is a meaningless thing. You shouldn't care where
the compiler stored your type on the stack or as a static, namespace-scope or
file-scope variable. The only case where it matters is if you malloc() a region
and want to create an object of type T there -- but malloc() is guaranteed to
return blocks that are suitably aligned for all primitive types and there's
aligned_alloc().

Here's what's important:
a) for any object or sub-object of type T, it must be suitably aligned so
that loading and storing it to memory can happen without hardware faults.

b) for any U containing T, alignof(U) >= alignof(T_in_U), where
template <typename T> union T_in_U { T member; };
(the same is valid for structs)

32-bit x86 does not violate the above. Any time you put a double, long double
or long long inside a struct or union, its alignment will respect the two
requirements above.

Additionally, given
sizeof(T1) == sizeof(T2) && alignof(T1_in_U) == alignof(T2_in_U)
then replacing a member of type T1 with T2 will not change the structure's
layout (ABI guarantee, not a standard one)

And finally, note that this is not guaranteed:
sizeof(T) == sizeof(std::atomic<T>)
alignof(T) == alignof(std::atomic<T>)

Larry Evans

unread,
Sep 27, 2017, 9:34:29 PM9/27/17
to std-dis...@isocpp.org
On 09/27/2017 06:35 PM, Thiago Macieira wrote:
> On quarta-feira, 27 de setembro de 2017 16:24:37 PDT Thiago Macieira wrote:
>>> For a union U with a non-static member of type T, is it guaranteed by the
>>> Standard that alignof(U) >= alignof(T)?
>>
>> No.
>
[snip]
> Here's what's important:
> a) for any object or sub-object of type T, it must be suitably aligned so
> that loading and storing it to memory can happen without hardware faults.
>
> b) for any U containing T, alignof(U) >= alignof(T_in_U), where
> template <typename T> union T_in_U { T member; };
> (the same is valid for structs)
>
> 32-bit x86 does not violate the above. Any time you put a double, long double
> or long long inside a struct or union, its alignment will respect the two
> requirements above.
>

IIUC, you're contradicting yourself. Above, in reply to the OP's question:

For a union U with a non-static member of type T,
is it guaranteed by the Standard that alignof(U) >= alignof(T)?

you say:

No.

Yet, in item b) just above, you reaffirm that
alignof(U) >= alignof(T_in_U)

OOPS. I see a slight difference, the OP had alignof(T) but your
statement above uses alignof(T_in_U). So under what circumstances
can alignof(T_in_U) != alignof(T)? I would think that the alignment
requirements of T is independent of whether it's in a struct, a union,
or by itself. What am I missing?

-regards,
Larry


-

Larry Evans

unread,
Sep 27, 2017, 9:57:20 PM9/27/17
to std-dis...@isocpp.org
On 09/27/2017 06:25 PM, Thiago Macieira wrote:
> On quarta-feira, 27 de setembro de 2017 15:54:44 PDT Larry Evans wrote:
>> /usr/bin/clang++-3.8 -c -O0 -gdwarf-2 -stdlib=libc++ -std=c++1z
>> -ftemplate-backtrace-limit=0 -fdiagnostics-show-template-tree
>> -fno-elide-type -fmacro-backtrace-limit=0 -c union.cpp
>
> You forgot -m32.
>

Thanks. I wasn't aware that was needed.
The -m32 option specifies generation of
32bit i386 code, and, when that option was used, the
assertion did fire. But this I don't understand because if
U is not put on an alignment suitable for it's contained
T member, then that contained member wouldn't be suitably
aligned. What am I missing? Hmm. Maybe the compiler
just adds padding to assure the T is suitably aligned
within the U? That seems to waste space; so, I'm
still puzzled :(



Chris Hallock

unread,
Sep 28, 2017, 12:14:47 AM9/28/17
to ISO C++ Standard - Discussion
On Wednesday, September 27, 2017 at 4:28:53 PM UTC-4, Myriachan wrote:
For a union U with a non-static member of type T, is it guaranteed by the Standard that alignof(U) >= alignof(T)?

Apparently not; according to [basic.align]/2, "[...] The alignment required for a type might be different when it is used as the type of a complete object and when it is used as the type of a subobject. [...] The result of the alignof operator reflects the alignment requirement of the type in the complete-object case."

Larry Evans

unread,
Sep 28, 2017, 5:27:17 AM9/28/17
to std-dis...@isocpp.org
On 09/27/2017 11:14 PM, Chris Hallock wrote:
> On Wednesday, September 27, 2017 at 4:28:53 PM UTC-4, Myriachan wrote:
>>
>> For a union U with a non-static member of type T, is it guaranteed by the
>> Standard that alignof(U) >= alignof(T)?
>>
>
> Apparently not; according to [basic.align]/2
> <http://eel.is/c++draft/basic.align#2>, "[...] The alignment required for a
> type might be different when it is used as the type of a complete object
> and when it is used as the type of a subobject. [...] The result of the
> alignof operator reflects the alignment requirement of the type in the
> complete-object case."
>
However, that does *not* say alignof(U) < alignof(T_within_U), it *only*
says that *maybe* alignof(T) != alignof(T_within_U). For all we know,
maybe alignof(T) <= alignof(T_within_U) and alignof(U) >=
alignof(T_within_U) which implies alignof(U) >= alignof(T).
However, that doesn't explain why the assert fires when -m32 is passed
as the compiler flag. When the compile and link step are separated
and the assert is disabled, the compile succeeds but the link
fails on my machine with error msgs as shown here:

--{--cut here--
/home/evansl/dwnlds/gcc/5.2.0/install/bin/g++ -std=c++17 -m32 -c union.cpp
/home/evansl/dwnlds/gcc/5.2.0/install/bin/g++ -m32 union.o
/usr/bin/ld: cannot find crt1.o: No such file or directory
/usr/bin/ld: cannot find crti.o: No such file or directory
/usr/bin/ld: cannot find -lstdc++
/usr/bin/ld: cannot find -lm
/usr/bin/ld: cannot find -lgcc_s
--}--cut here--

Maybe I just need to install some sort of -m32 libraries to get the
link to succeed?


Chris Hallock

unread,
Sep 28, 2017, 1:48:28 PM9/28/17
to ISO C++ Standard - Discussion, cpplj...@suddenlink.net


On Thursday, September 28, 2017 at 5:27:17 AM UTC-4, Larry Evans wrote:
On 09/27/2017 11:14 PM, Chris Hallock wrote:
> On Wednesday, September 27, 2017 at 4:28:53 PM UTC-4, Myriachan wrote:
>>
>> For a union U with a non-static member of type T, is it guaranteed by the
>> Standard that alignof(U) >= alignof(T)?
>>
>
> Apparently not; according to [basic.align]/2
> <http://eel.is/c++draft/basic.align#2>, "[...] The alignment required for a
> type might be different when it is used as the type of a complete object
> and when it is used as the type of a subobject. [...] The result of the
> alignof operator reflects the alignment requirement of the type in the
> complete-object case."
>
However, that does *not* say alignof(U) < alignof(T_within_U), it *only*
says that *maybe* alignof(T) != alignof(T_within_U).  For all we know,
maybe alignof(T) <= alignof(T_within_U) and alignof(U) >=
alignof(T_within_U) which implies alignof(U) >= alignof(T).
However, that doesn't explain why the assert fires when -m32 is passed
as the compiler flag.

To summarize, GCC can handle long long/double objects being aligned to 4 bytes on IA-32, but for whatever reason prefers 8-byte alignment if they're complete objects (maybe the idea is to prefer 8-byte alignment for speed, but optimize for size within structs). The alignment is different on x86-64 because the ABI (I'm guessing) flatly requires the alignment to be 8 bytes regardless of subobject status.

When the compile and link step are separated
and the assert is disabled, the compile succeeds but the link
fails on my machine with error msgs as shown here: [...]


Maybe I just need to install some sort of -m32 libraries to get the
link to succeed?

For Debian flavors, install g++-<version>-multilib, where <version> is your GCC version.

Thiago Macieira

unread,
Sep 28, 2017, 1:57:31 PM9/28/17
to std-dis...@isocpp.org
See my other email with the explanation.

Thiago Macieira

unread,
Sep 28, 2017, 2:01:55 PM9/28/17
to std-dis...@isocpp.org
What I meant is for a given

union U {
some other members
T t;
};
alignof(U) >= alignof(union { T t; });

The alignment of a naked T is irrelevant. What matters is the alignment of T
inside a struct or class. That is what is used to calculate the alignment of
the whole object as well as specify the layout of the struct.

If two T are global or on the heap, then their addresses are unpredictable and
you can't compare them for order (only equality). What's more, the compiler
often applies larger alignment requirements even for smaller types, if it
wants to. I've seen 4-byte objects aligned to 16 or even 64 bytes.

Hyman Rosen

unread,
Sep 28, 2017, 2:04:09 PM9/28/17
to std-dis...@isocpp.org
In N4687 [basic.align] 6.11¶2, the standard offers the example
struct B { long double d; };
struct D : virtual B { char c; };

and says
When D is the type of a complete object, it will have a subobject of type B, so it must be aligned appropriately for a long double.


Thiago Macieira

unread,
Sep 28, 2017, 2:06:23 PM9/28/17
to std-dis...@isocpp.org
On quinta-feira, 28 de setembro de 2017 10:48:28 PDT Chris Hallock wrote:
> To summarize, GCC can handle long long/double objects being aligned to 4
> bytes on IA-32, but for whatever reason prefers 8-byte alignment if they're
> complete objects (maybe the idea is to prefer 8-byte alignment for speed,
> but optimize for size within structs). The alignment is different on x86-64
> because the ABI (I'm guessing) flatly requires the alignment to be 8 bytes
> regardless of subobject status.

It's not optimisation. It's ABI compatibility: remember, the x86 SysV ABI is
from the 1980s.

It was painful enough when GCC started requiring a 16-byte aligned stack on
function entry back in the early 2000s.

See http://www.sco.com/developers/devspecs/abi386-4.pdf (yes, SCO!)

Permalink: https://refspecs.linuxfoundation.org/elf/gabi41.pdf

Hyman Rosen

unread,
Sep 28, 2017, 2:06:33 PM9/28/17
to std-dis...@isocpp.org

Sorry, fat-fingered that.

I meant to add that this example strongly implies that the class should have an alignment no less than its strictest-aligned member. 

Myriachan

unread,
Sep 28, 2017, 2:16:19 PM9/28/17
to ISO C++ Standard - Discussion
Is taking a pointer to the subobject legal, then?  The pointer would not be properly aligned.

Melissa

Thiago Macieira

unread,
Sep 28, 2017, 4:51:50 PM9/28/17
to std-dis...@isocpp.org
On quinta-feira, 28 de setembro de 2017 11:16:19 PDT Myriachan wrote:
> On Wednesday, September 27, 2017 at 9:14:47 PM UTC-7, Chris Hallock wrote:
> > On Wednesday, September 27, 2017 at 4:28:53 PM UTC-4, Myriachan wrote:
> >> For a union U with a non-static member of type T, is it guaranteed by the
> >> Standard that alignof(U) >= alignof(T)?
> >
> > Apparently not; according to [basic.align]/2
> > <http://eel.is/c++draft/basic.align#2>, "[...] The alignment required for
> > a type might be different when it is used as the type of a complete object
> > and when it is used as the type of a subobject. [...] The result of the
> > alignof operator reflects the alignment requirement of the type in the
> > complete-object case."
>
> Is taking a pointer to the subobject legal, then? The pointer would not be
> properly aligned.

Again, the question is what "properly aligned" mean. From the HW point of
view, it's properly aligned if the load, store and modify operations that the
compiler will issue will succeed without an alignment fault/trap. In that
case, yes, they will be properly aligned. Since x86 didn't originally require
data alignment, unlike the RISC machines of the time, and no one turns on the
AC flag anyway, the required alignment for all types on x86 is actually 1.

But there's also "optimally aligned", which that's a completely different
subject. The optimal alignment of a 1-byte spinlock is 64 bytes, for example
(more specifically: no other spinlocks on the same cacheline). For 8- and 12-
byte quantities, the optimal alignment is 8 bytes. That's what alignof(double)
has returned for the past 15 years.

But it didn't use to. In the olden days, it returned 4. So structures
containing doubles and long longs were also aligned to 4 and the padding
inside those structures was suitable for that. That cannot change.

Chris Hallock

unread,
Sep 28, 2017, 5:58:01 PM9/28/17
to ISO C++ Standard - Discussion
IMO, the pointer ought to be classified as "properly aligned" because it points to a properly aligned object. Even the Standard is confused about this terminology because it uses ambiguous terms like "the alignment requirement of T". That term is ambiguous because, strictly speaking, [basic.align]/2 lets a type T have any number of alignment requirements — one for the complete object case (queriable with alignof), plus an unspecified set of alignments for any number of subobject scenarios.

Myriachan

unread,
Sep 29, 2017, 2:27:58 PM9/29/17
to ISO C++ Standard - Discussion
Yeah, this makes sense.  Perhaps some part of the wording should be changed to reflect this.

Interestingly, page 186 of Kernighan and Ritchie's "The C Programming Language" second edition says the opposite, and shows how to use a union to force alignment.  At least there is a simple way around this in modern C/C++: alignas(T).

Melissa

Richard Smith

unread,
Oct 2, 2017, 12:35:10 AM10/2/17
to std-dis...@isocpp.org
This is core issue 1879, whose resolution is that the alignof operator provides the minimum alignment, not the preferred alignment. So the implementations that return 8 for alignof(unsigned long long) and 4 for alignof(Counter) are wrong; they should return 4 for alignof(unsigned long long) on those targets.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+unsubscribe@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.

Reply all
Reply to author
Forward
0 new messages