Search code examples
cpointersstructstrict-aliasing

Strict aliasing rule with incompatible but same structs


Does this code violate strict aliasing rule? Why or why not?

#include <stdlib.h>

#define STRUCT struct {int x; char y;}
typedef STRUCT my_struct_t;

void get(my_struct_t *data, int *x, char *y)
{
    *x = data->x;
    *y = data->y;
}

int main(void)
{
    STRUCT *data = malloc(sizeof(*data));
    data->x=100;
    data->y=55;

    int x;
    char y;
    get((my_struct_t*)data, &x, &y);

    return 0;
}

Since structs are not compatible because they don't share a tag my guess would be that this code violates the strict aliasing rule.


Solution

  • The structure pointer data is pointing to a different type from my_struct_t because you have struct { … } *data = …; in main() (after macro expansion), and each time you have struct { … }, you are defining a new type distinct from any other type, even if those structure types have the same members.

    C11 §6.7.2.1 Structure and union specifiers ¶8 says:

    The presence of a struct-declaration-list in a struct-or-union-specifier declares a new type, within a translation unit.

    The italicized terms are part of the syntax of the structure (and union) grammar.

    Consequently, you are casting between types with the call

    get((my_struct_t*)data, &x, &y);
    

    In theory, that means undefined behaviour. In practice, it will work with almost any compiler.

    I tried using both GCC 11.2.0 and Apple Clang 14.0.0 with extensive options on your code, which I changed to specify static before the definition of get() to avoid errors for "no previous prototype for 'get'":

    $ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
          -fno-common -pedantic -pedantic-errors -fstrict-aliasing -Wstrict-aliasing=2 \
          -c sa43.c
    $ clang -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
            -fno-common -pedantic -Weverything -Wno-nullability-extension \
            -Wno-nullability-completeness -pedantic-errors -fstrict-aliasing \
            -Wstrict-aliasing=2 -c sa43.c
    $
    

    The -Weverything flag for Clang triggers complaints about padding (-Wpadded, could be suppressed by -Wno-padded). That then exposes a complaint about using /usr/local/include because of -Wpoison-system-directories. The Clang -Weverything option is not all that helpful because it activates too many warnings, but adding -Wno-poison-system-directories (as well as -Wno-padded) allows the code to compile cleanly.

    The -Wno-nullability-extension and -Wno-nullability-completeness are an extremely irritating necessity because otherwise the system headers fail horribly on my MacBook Pro running macOS Monterey 12.6.3.

    So, de facto, your code is OK — but de jure, you are casting between different types.