[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