[Bug c/124801] New: With aligned attribute, two types can have each distinct known layouts and be compatible types
pascal_cuoq at hotmail dot com
gcc-bugzilla@gcc.gnu.org
Tue Apr 7 11:18:45 GMT 2026
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124801
Bug ID: 124801
Summary: With aligned attribute, two types can have each
distinct known layouts and be compatible types
Product: gcc
Version: 15.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c
Assignee: unassigned at gcc dot gnu.org
Reporter: pascal_cuoq at hotmail dot com
Target Milestone: ---
When __attribute__((aligned(XX))) is used on a struct or typedef, the resulting
type is treated as compatible with the original (in the case of a typedef) or
another otherwise identical definition without the attribute (for a struct, in
C23).
Of course __attribute__((aligned(XX))) is a compiler extension and the C
standard doesn't apply to programs that use it, but it seems to me that it is
an deep underlying principle of every C standard that when two types have known
layout and are compatible, they should have the same layout. This is
“underlying” enough not to be stated explicitly, but for instance, when C23
6.7.1:5 says “All declarations in the same scope that refer to the same object
or function shall specify compatible types”, it does not go on to clarify how
the layout of the object should be chosen if the multiple declarations in the
same scope are for compatible types that correspond to distinct layouts.
GCC's behavior means that the following program is accepted without warning
and, in all probability, crashes on the assert call:
https://gcc.godbolt.org/z/o7eq7sj6j
#include <assert.h>
typedef __attribute__((aligned(16))) unsigned char c0;
int y = sizeof(c0);
void f(c0 *p) {
assert(!((unsigned long)p & 15));
*p = 0;
}
int main(int argc, char *argv[]) {
unsigned char c;
switch (argc) {
case 1:
f(&c);
break;
case 2: {
void (*p)(unsigned char*) = f;
p(&c);
break;
}
}
}
Looking at Clang's behavior here suggest a possible way out now that the
extension is what it is (and probably was chosen so because making c0
incompatible with unsigned char has its own inconveniences): treat c0 and
unsigned char as compatible but emit a warning when this compatibility comes
into play.
The following example with structs is more spectacular:
https://gcc.godbolt.org/z/rbx7Wx9Er
#include <assert.h>
struct __attribute__((aligned(16))) s { unsigned char c; };
struct s s0;
int y = sizeof(s0);
void f(struct s *p) {
assert(!((unsigned long)p & 15));
*p = s0;
}
int main(int argc, char *argv[]) {
volatile unsigned char c1;
struct s { unsigned char c; } s;
volatile unsigned char c2;
c1 = 0xaa;
c2 = 0xcc;
switch (argc) {
case 1:
f(&s);
break;
case 2: {
void (*p)(struct s*) = f;
p(&s);
break;
}
}
assert(c1 == 0xaa && c2 == 0xcc);
}
The program contains no pointer cast other than the one in the assert—remove it
if you like—, it is accepted without warning, and it writes 16 bytes to s at
14(%rsp) in main:
f:
testb $15, %dil
jne .L6
movdqa s0(%rip), %xmm0
movaps %xmm0, (%rdi)
ret
…
main:
…
movb $-86, 15(%rsp)
movb $-52, 13(%rsp)
…
leaq 14(%rsp), %rdi
call f
…
y:
.long 16
s0:
.zero 16
More information about the Gcc-bugs
mailing list