Type-Punning a pointer inside of incompatible but equivalent structs correctly

311 views Asked by At

My goal is something like this:

void alloc(*x)
{
    x->ptr = malloc(100);
}

int main()
{
    struct { int z; int *ptr; } foo;
    struct { int z; double *ptr; } bar;

    alloc(&foo);
    alloc(&bar);
    return 0;
}

The function alloc shall allocate memory for different kind of structs, which are all basically the same, but use different pointers.

My attempts for a solution would look like this:

struct generic {
    int z;
    void *ptr;
};

void alloc(void *x)
{
    struct generic *tmp = x;
    tmp->ptr = malloc(100);
}

or:

union generic {
    void *p;
    struct {
        int z;
        void *ptr;
    } *g;
};

void alloc(void *x)
{
    union generic tmp = {.p = x};
    tmp.g->ptr = malloc(100);
}

Are they correct or do they break strict-aliasing as the actual parameters are not compatible with the generic-struct and dereferencing x or tmp.g is not valid?

Further, granted that this was violating strict-aliasing, how would it have an impact? Strict-aliasing is used for not reloading specific values under the assumption that they could not have been modified when they weren't aliased in a correct manner (char*, void*, union, compatible type). alloc() is called with a void-pointer as its parameter which may alias, so the caller can't assume that the underlying data won't change. Inside of alloc() I would exclusively use the type-punned pointer. So where could something go wrong in this scenario by not reloading correctly?

2

There are 2 answers

0
netcat On BEST ANSWER

Neither snippet is portable.

As for the first one, tmp's type is not compatible with that of the object pointed to by x, namely struct { int z; int *ptr; } or struct { int z; double *ptr; }. Thus dereferencing tmp is in violation of the strict aliasing rules.

The problem with the second snippet is that having a union of pointers with incompatible types would only tell the compiler that the pointers themselves are aliased, not the objects they point to, so it's still breaking strict aliasing (for more information on strict aliasing, see here).

Also what both snippets ignore is that different types of pointers can actually have different internal representations (although rare), that's why type-punning a double pointer to a void pointer and later on using it as a double pointer again results in the pointer's bit pattern getting simply reinterpreted, but not converted, possibly yielding a trap representation and hence an invalid pointer (for more information on internal pointer representations, see here).

In conclusion, the overall approach of both snippets is not portable and needs to be rethought.

0
martinkunev On

You can put the second member in a struct (to avoid pointer compatibility problems).

C99 6.2.5.26

All pointers to structure types shall have the same representation and alignment requirements as each other.

Then you can use a union for type punning (to avoid strict aliasing problems).

struct generic
{
    int z;
    union
    {
        struct {int i;} i;
        struct {double d;} d;
    } *ptr;
};

void alloc(struct generic *x)
{
    x->ptr = malloc(100);
}

int main(void)
{
    struct generic foo;
    struct generic bar;
    alloc(&foo);
    alloc(&bar);
    return 0;
}

You get the inconvenience of having to access the member of the struct. If you use gcc or any C11-compatible compiler, you can bypass that problem with anonymous (unnamed) struct members.

There seems to be no way this won't work with a conforming implementation.