Search code examples
c++reinterpret-cast

Difference in compiler warnings when "reinterpret_cast" is used on pointers vs references


Suppose i have two structs

struct B 
{ 
int n;
};

struct C
{ 
int k;
};

and

B b = {};
C& c = reinterpret_cast<C&>(b); //Not Ok , compiler(gcc 8.5 with -O2 -Wall) is not happy 
C *c1 = reinterpret_cast<C*>(&b); //Okay, compiler(gcc 8.5 with -O2 -Wall) is happy

sample code:

#include<new>
#include<iostream>



int main() {


struct B { int n; };


struct C
 { int k; }
;

B b = {};
C c = reinterpret_cast<C&>(b);
C *c1 = reinterpret_cast<C*>(&b);

printf("b.n is %d\n",b.n);
printf("c.k is %d\n",c.k);
printf("c1.k is %d\n",c1->k);
    return 0;
}

Can somebody help me to understand why there is difference in behavior for above code though I believe they are functionally same?

I expected compiler to be happy even on references as I know both types have same memory alignment.

I get the following warning

<source>:142:29: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
 C c = reinterpret_cast<C&>(b);

see https://godbolt.org/z/4o7bhGrYP


Solution

  • The bullet for reinterpret_cast that applies here is:

    1. Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast<cv T2*>(static_cast<cv void*>(expression)) (which implies that if T2's alignment requirement is not stricter than T1's, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if allowed by the type aliasing rules (see below)

    and

    1. An lvalue (until C++11) glvalue (since C++11) expression of type T1 can be converted to reference to another type T2. The result is that of *reinterpret_cast<T2*>(p), where p is a pointer of type “pointer to T1” to the object designated by expression. No temporary is created, no copy is made, no constructors or conversion functions are called. The resulting reference can only be accessed safely if allowed by the type aliasing rules (see below)

    The type aliasing rule says

    Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

    The only bullet that might apply is:

    • AliasedType and DynamicType are similar.

    And similar is one of the following:

    • they are the same type; or
    • they are both pointers, and the pointed-to types are similar; or
    • they are both pointers to member of the same class, and the types of the pointed-to members are similar; or
    • they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar. (until C++20)
    • they are both arrays of the same size or at least one of them is array of unknown bound, and the array element types are similar.

    A and C are not arrays. A* and C* are not similar.

    Ergo, both c and c1 are pretty useless. c cannot be accessed and c1 can only be cast back to a A*. Anything else results in undefined behavior.

    The compiler is not mandated to warn or error on undefined behavior. It warns for the reference because there is nothing you can do with the reference. It does not warn for the pointer, because such cast can be useful (but only as intermediate result, only to cast back to B*).

    reinterpret_cast is not a any-to-any cast that you can apply just because A and C look the same. If you want to convert a B to a C you need a proper conversion.