54

I use a code where I cast an enum* to int*. Something like this:

enum foo { ... }
...
foo foobar;
int *pi = reinterpret_cast<int*>(&foobar);

When compiling the code (g++ 4.1.2), I get the following warning message:

dereferencing type-punned pointer will break strict-aliasing rules

I googled this message, and found that it happens only when strict aliasing optimization is on. I have the following questions:

  • If I leave the code with this warning, will it generate potentially wrong code?
  • Is there any way to work around this problem?
  • If there isn't, is it possible to turn off strict aliasing from inside the source file (because I don't want to turn it off for all source files and I don't want to make a separate Makefile rule for this source file)?

And yes, I actually need this kind of aliasing.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
petersohn
  • 11,292
  • 13
  • 61
  • 98

5 Answers5

62

In order:

  • Yes. GCC will assume that the pointers cannot alias. For instance, if you assign through one then read from the other, GCC may, as an optimisation, reorder the read and write - I have seen this happen in production code, and it is not pleasant to debug.

  • Several. You could use a union to represent the memory you need to reinterpret. You could use a reinterpret_cast. You could cast via char * at the point where you reinterpret the memory - char * are defined as being able to alias anything. You could use a type which has __attribute__((__may_alias__)). You could turn off the aliasing assumptions globally using -fno-strict-aliasing.

  • __attribute__((__may_alias__)) on the types used is probably the closest you can get to disabling the assumption for a particular section of code.

For your particular example, note that the size of an enum is ill defined; GCC generally uses the smallest integer size that can be used to represent it, so reinterpreting a pointer to an enum as an integer could leave you with uninitialised data bytes in the resulting integer. Don't do that. Why not just cast to a suitably large integer type?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
moonshadow
  • 86,889
  • 7
  • 82
  • 122
  • 13
    Just a warning, that it's unspecified behaviour to use an union by writing to one member and reading from another, according to the C++ standard. So it may work, it may not. – JPvdMerwe Nov 12 '10 at 10:13
  • 4
    @JPvdMerwe: GCC explicitly supports it though. So does MSVC. – jalf Nov 12 '10 at 12:43
  • 19
    "cast via `char*`" - your point isn't wrong, but "via" may be misleading. You can read the bytes one by one through a `char*` or `unsigned char*`. But `reinterpret_cast(reinterpret_cast(&foobar))` still breaks strict aliasing, even though I've "cast via `char*`". If X can alias Y, and Y can alias Z (Y being the `char*`), then under strict aliasing rules it does not necessarily follow that X can alias Z. – Steve Jessop Nov 12 '10 at 13:35
  • 2
    @JPvdMerwe: see accepted answer to http://stackoverflow.com/questions/8511676/portable-data-reinterpretation – moonshadow May 09 '13 at 11:46
  • 1
    Avoiding the reinterpret_cast by static_casting to void* and then to the desired type will not produce the warning. Will it still invoke undefined behavior, though? – antred May 13 '15 at 15:35
  • 1
    @antred: yes, it will. – moonshadow May 15 '15 at 21:49
14

You could use the following code to cast your data:

template<typename T, typename F>
struct alias_cast_t
{
    union
    {
        F raw;
        T data;
    };
};

template<typename T, typename F>
T alias_cast(F raw_data)
{
    alias_cast_t<T, F> ac;
    ac.raw = raw_data;
    return ac.data;
}

Example usage:

unsigned int data = alias_cast<unsigned int>(raw_ptr);
Markus Lenger
  • 521
  • 5
  • 7
  • 8
    Adding `static_assert(sizeof(T) == sizeof(F), "Cannot cast types of different sizes");` to `alias_cast` would improve safety of this code. – Johannes Jendersie Mar 18 '14 at 15:23
  • If this's used where T and F are pointer types, isn't this still UB? And if they're non-pointer types, isn't this the same as `reinterpret_cast(f)`, sans any additional checking? – Noein Aug 17 '17 at 12:38
11

But why are you doing this? It will break if sizeof(foo) != sizeof(int). Just because an enum is like an integer does not mean it is stored as one.

So yes, it could generate "potentially" wrong code.

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • I checked, they are the same size. – petersohn Nov 12 '10 at 09:32
  • 5
    @petersohn Maybe on your machine, that doesn't mean anything. – Šimon Tóth Nov 12 '10 at 09:36
  • 5
    @petersohn: what about now? Are they still the same size? You have no guarantee that just because it worked at one point, it'll *keep* working. – jalf Nov 12 '10 at 12:44
  • 2
    @jalf: Yes, they're still the same size. It's implementation-defined what the underlying type of an enum is, not unspecified. I haven't actually checked GCC's docs on the subject, but I'm pretty sure they don't say, "int on Mondays, Wednesdays and Fridays but short on Tuesdays, Thursdays, weekends and public holidays". So I strongly suspect petersohn actually does have a guarantee, for the compiler he says he's using, possibly subject to changes from platform to platform. It's just that nobody has bothered to look it up and quote it. – Steve Jessop Nov 12 '10 at 13:40
  • 1
    I could manage to change the implementation so that now it is enough to static_cast between enum and int, not enum* and int*. – petersohn Nov 12 '10 at 13:41
  • @Steve: true. :) @petersohn: that sounds much more sensible. – jalf Nov 12 '10 at 13:48
5

Have you looked into this answer ?

The strict aliasing rule makes this setup illegal, two unrelated types can't point to the same memory. Only char* has this privilege. Unfortunately you can still code this way, maybe get some warnings, but have it compile fine.

Community
  • 1
  • 1
icecrime
  • 74,451
  • 13
  • 99
  • 111
3

Strict aliasing is a compiler option, so you need to turn it off from the makefile.

And yes, it can generate incorrect code. The compiler will effectively assume that foobar and pi aren't bound together, and will assume that *pi won't change if foobar changed.

As already mentioned, use static_cast instead (and no pointers).

Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151