ccastingunionsanonymous-struct

Does dereferencing a cast to an anonymous structure pointer violate strict aliasing?


I have heard conflicting things about the extent to which the C standards guarantee structure layout consistency. Arguments for a limited extent have mentioned strict aliasing rules. For example, compare these two answers: https://stackoverflow.com/a/3766251/1306666 and https://stackoverflow.com/a/3766967/1306666.

In the following code I assume in all structures foo, bar, and struct { char *id; } that char *id is in the same place, making it safe to cast between them if it is the only member accessed.

Regardless of whether the cast will ever result in an error, does it violate strict aliasing rules?

#include <string.h>

struct foo {
    char *id;
    int a;
};

struct bar {
    char *id;
    int x, y, z;
};

struct list {
    struct list *next;
    union {
        struct foo *foop;
        struct bar *barp;
        void *either;
    } ptr;
};

struct list *find_id(struct list *l, char *key)
{
    while (l != NULL) {
                    /* cast to anonymous struct and dereferenced */
        if (!strcmp(((struct { char *id; } *)(l->ptr.either))->id, key))
            return l;
        l = l->next;
    }
    return NULL;
}


gcc -o /dev/null -Wstrict-aliasing test.c

Note gcc gives no errors.


Solution

  • Yes, there are multiple aliasing-related issues in your program. The use of the lvalue with anonymous structure type, which does not match the type of the underlying object, results in undefined behavior. It could be fixed with something like:

    *(char**)((char *)either + offsetof(struct { ... char *id; ... }, id))
    

    if you know the id member is at the same offset in all of them (e.g. they all share same prefix). But in your specific case where it's the first member you can just do:

    *(char**)either
    

    because it's always valid to convert a pointer to a struct to a pointer to its first member (and back).

    A separate issue is that your use of the union is wrong. The biggest issue is that it assumes struct foo *, struct bar *, and void * all have the same size and representation, which is not guaranteed. Also, it's arguably undefined to access a member of the union other than the one which was previously stored, but as a result of interpretations in defect reports, it's probably safe to say it's equivalent to a "reinterpret cast". But that gets you back to the issue of wrongly assuming same size/representation.

    You should just remove the union, use a void * member, and convert the value (rather than reinterpret the bits) to the right pointer type to access the pointed-to structure (struct foo * or struct bar *) or its initial id field (char *).