Why is this claimed dereferencing type-punned pointer warning compiler-specific?

This code is invalid per the C Standard, so it might work in some cases, but is not necessarily portable.

The "strict aliasing rule" for accessing a value via a pointer that has been cast to a different pointer type is found in 6.5 paragraph 7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  • a type compatible with the effective type of the object,

  • a qualified version of a type compatible with the effective type of the object,

  • a type that is the signed or unsigned type corresponding to the effective type of the object,

  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

  • an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

  • a character type.

In your *obj = NULL; statement, the object has effective type Foo* but is accessed by the lvalue expression *obj with type void*.

In 6.7.5.1 paragraph 2, we have

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

So void* and Foo* are not compatible types or compatible types with qualifiers added, and certainly don't fit any of the other options of the strict aliasing rule.

Although not the technical reason the code is invalid, it's also relevant to note section 6.2.5 paragraph 26:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

As for the differences in warnings, this is not a case where the Standard requires a diagnostic message, so it's just a matter of how good the compiler or its version is at noticing potential issues and pointing them out in a helpful way. You noticed optimization settings can make a difference. This is often because more information is internally generated about how various pieces of the program actually fit together in practice, and that extra information is therefore also available for warning checks.


Dereferencing a type punned pointer is UB and you can't count on what will happen.

Different compilers generate different warnings, and for this purpose different versions of the same compiler can be considered as different compilers. This seems a better explanation for the variance you see than a dependence on the architecture.

A case which may help you understand why type punning in this case can be bad is that your function won't work on an architecture for which sizeof(Foo*) != sizeof(void*). That is authorized by the standard although I don't know any current one for which this is true.

A workaround would be to use a macro instead of a function.

Note that free accepts null pointers.


A value of type void** is a pointer to an object of type void*. An object of type Foo* is not an object of type void*.

There is an implicit conversion between values of type Foo* and void*. This conversion may change the representation of the value. Similarly, you can write int n = 3; double x = n; and this has the well-defined behavior of setting x to the value 3.0, but double *p = (double*)&n; has undefined behavior (and in practice will not set p to a “pointer to 3.0” on any common architecture).

Architectures where different types of pointers to objects have different representations are rare nowadays, but they are permitted by the C standard. There are (rare) old machines with word pointers which are addresses of a word in memory and byte pointers which are addresses of a word together with a byte offset in this word; Foo* would be a word pointer and void* would be a byte pointer on such architectures. There are (rare) machines with fat pointers which contain information not only about the address of the object, but also about its type, its size and its access control lists; a pointer to a definite type might have a different representation from a void* which needs additional type information at runtime.

Such machines are rare, but permitted by the C standard. And some C compilers take advantage of the permission to treat type-punned pointers as distinct to optimize code. The risk of pointers aliasing is a major limitation to a compiler's ability to optimize code, so compilers tend to take advantage of such permissions.

A compiler is free to tell you that you're doing something wrong, or to quietly do what you didn't want, or to quietly do what you wanted. Undefined behavior allows any of these.

You can make freefunc a macro:

#define FREE_SINGLE_REFERENCE(p) (free(p), (p) = NULL)

This comes with the usual limitations of macros: lack of type safety, p is evaluated twice. Note that this only gives you the safety of not leaving dangling pointers around if p was the single pointer to the freed object.


A void * is treated specially by the C standard in part because it references an incomplete type. This treatment does not extend to void ** as it does point to a complete type, specifically void *.

The strict aliasing rules say you can't convert a pointer of one type to a pointer of another type and subsequently dereference that pointer because doing so means reinterpreting the bytes of one type as another. The only exception is when converting to a character type which allows you to read the representation of an object.

You can get around this limitation by using a function-like macro instead of a function:

#define freeFunc(obj) (free(obj), (obj) = NULL)

Which you can call like this:

freeFunc(f);

This does have a limitation however, because the above macro will evaluate obj twice. If you're using GCC, this can be avoided with some extensions, specifically the typeof keyword and statement expressions:

#define freeFunc(obj) ({ typeof (&(obj)) ptr = &(obj); free(*ptr); *ptr = NULL; })