I'm on a x86_64 machine, with Ubuntu 16.04 and gcc 5.4.0.
I've encountered this somewhat strange behavior today.
$ cat main.c
#include <stdio.h>
struct dd { double d1; double d2; };
int main()
{
printf("%d\n", (int)__alignof__(double));
printf("%d\n", (int)__alignof__(struct dd));
}
$ gcc -m32 -o main main.c && ./main
8
4
__alignof__
returns 8 for double
and 4 for struct dd
. I think it is strange for a struct
to be aligned in smaller unit than its members. This behavior is even contrary to the ISO C standard mentioned in the gcc 5.4.0 doc:
Note that the alignment of any given
struct
orunion
type is required by the ISO C standard to be at least a perfect multiple of the lowest common multiple of the alignments of all of the members of thestruct
orunion
in question.
What may be the reason for this behavior? Is aligning this struct
to 4 byte an optimized way to access memory?
The document that specifies how this should behave on your architecture is here, in the i386 System V psABI. (Current revision here, see also the x86 tag wiki). In it we can read that the required alignment of double is 4. But it has this interesting note:
The Intel386 architecture does not require doubleword alignment for double-precision values. Nevertheless, for data structure compatibility with other Intel architectures, compilers may provide a method to align double-precision values on doubleword boundaries.
A compiler that provides the doubleword alignment mentioned above can generate code (data structures and function calling sequences) that do not conform to the Intel386 ABI. Programs built with the doubleword alignment facility can thus violate conformance to the Intel386 ABI. See ‘‘Aggregates and Unions’’ below and ‘‘Function Calling Sequence’’ later in this chapter for more information.
GCC doesn't want to violate the ABI for structs (where alignment is quite relevant) so it correctly uses the alignment of 4 for doubles inside structs.
This behavior is even contrary to the ISO C standard
ISO C is completely irrelevant here since __alignof__
is not part of any C standard. The compiler could do anything, like fetch a picture of a cat from the internet and show it to you, and that would be a behavior completely compliant with the C standard.
C11 does specify an _Alignof
operator though. Interestingly enough, if we use the _Alignof
operator that is part of the C11 standard, GCC reports other (correct) numbers:
$ cat foo.c
#include <stdio.h>
struct dd { double d1; double d2; };
int main()
{
printf("%d\n", (int)__alignof__(double));
printf("%d\n", (int)__alignof__(struct dd));
}
$ cc -m32 -o foo foo.c && ./foo
8
4
$ ed foo.c
[...]
$ cat foo.c
#include <stdio.h>
struct dd { double d1; double d2; };
int main()
{
printf("%d\n", (int)_Alignof(double));
printf("%d\n", (int)_Alignof(struct dd));
}
$ cc -m32 -o foo foo.c && ./foo
4
4
The wording of the C standard isn't very specific about what should happen in ABIs where types inside structs have lower alignment than they do outside.
After careful reading of the standard's wording, and some debate, gcc developers decided that _Alignof
should tell you the minimum alignment that you will ever see for a value of that type in a strict C11 program (https://gcc.gnu.org/ml/gcc-patches/2013-12/msg00435.html). (This is what you want for a use-case like writing a garbage collector that scans blocks of memory for potential pointers.) Note that C11 doesn't include __attribute__((packed))
, and casting unaligned pointers is UB.
This mailing list post explains why they changed C11 _Alignof
, but not C++ alignof
or the GNU __alignof__
extension.
GNU C's __alignof__
continues to mean how gcc will align that type as a global or local, outside of a struct. i.e. the maximum/recommended alignment. The current version of the i386 SysV ABI doesn't say anything about aligning double
to 8B ever; that's purely optional behaviour by current compilers for performance.
Having _Alignof(double) <= _Align(struct containing_double)
appears to satisfy all the requirements in the C11 standard, even though the preferred alignment for double
is 8B. double
works when crossing a 4B boundary, it's just slow if it crosses a cache-line or page.
(But note that _Atomic long long
doesn't "work" if it's not 8B aligned, so clang gives it 8B alignment even inside structs. Current gcc is broken for C11 stdatomic 8B types on the 32-bit SysV ABI, and will hopefully change to match clang.)
In clang, _Alignof
seems to be the same as __alignof__
. So it disagrees with gcc about the C11 operator (but not about struct layout, except for C11 stdatomic).
See some test cases on the Godbolt compiler explorer with gcc7.2 and clang4.0. Remove the -xc
to compile as C++ instead of C
somewhat related: gcc7 increased the alignment of max_align_t
in 32-bit from 8 to 16, for _Float128
, but malloc(8)
or strdup("abc")
might still return only 8B-aligned blocks.
gcc's stddef.h
implements max_align_t
with a struct with members like
long long __max_align_ll __attribute__((__aligned__(__alignof__(long long))));
to make sure that the resulting struct really does have as large an alignment requirement (_Alignas
) as its members. It also has long double
and __float128
members.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With