6
#include <iostream>

int main(int argc, char * argv[])
{
    int a = 0x3f800000;

    std::cout << a << std::endl;

    static_assert(sizeof(float) == sizeof(int), "Oops");

    float f2 = *reinterpret_cast<float *>(&a);

    std::cout << f2 << std::endl;

    void * p = &a;
    float * pf = static_cast<float *>(p);
    float f3 = *pf;

    std::cout << f3 << std::endl;

    float f4 = *static_cast<float *>(static_cast<void *>(&a));

    std::cout << f4 << std::endl;
}

I get the following info out of my trusty compiler:

me@Mint-VM ~/projects $ g++-5.3.0 -std=c++11 -o pun pun.cpp -fstrict-aliasing -Wall
pun.cpp: In function ‘int main(int, char**)’:
pun.cpp:11:45: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     float f2 = *reinterpret_cast<float *>(&a);
                                             ^
pun.cpp:21:61: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     float f4 = *static_cast<float *>(static_cast<void *>(&a));
                                                             ^
me@Mint-VM ~/projects $ ./pun
1065353216
1
1
1
me@Mint-VM ~/projects $ g++-5.3.0 --version
g++-5.3.0 (GCC) 5.3.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I don't really understand when and why I get type-punned errors in some places and not in others.

So, strict aliasing:

Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)

Line 11 claims I'm breaking strict-aliasing. I don't see a case where this can possibly hurt anything - the pointer "comes into existence", is immediately dereferenced, then thrown away. In all likelyhood, this will compile down to zero instructions. This seems like absolutely no risk - I'm telling the compiler EXACTLY what I want.

Lines 15-16 proceed to NOT elicit a warning, even though the pointers to the same memory location are now here to stay. This appears to be a bug in gcc.

Line 21 elicits the warning, showing that this is NOT limited to just reinterpret_cast.

Unions are no better (emphasis mine):

...it's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.

This link talks about using memcpy, but that seems to just hide what you're really trying to accomplish.

For some systems, it is a required operation to write a pointer to an int register, or receive an incoming byte stream and assemble those bytes into a float, or other non-integral type.

What is the correct, standard-conforming way of doing this?

phuclv
  • 37,963
  • 15
  • 156
  • 475
bizaff
  • 169
  • 7
  • 1
    Anton's answer is absolutely correct. It is the only portable way, and it is the only way that is guaranteed to work. There is not even any performance or memory overhead, because the optimiser will see what you're trying to do and in all likelihood, won't even copy any memory. You'll have to trust us on this until you have read and understood the standard a few times. – Richard Hodges Apr 04 '16 at 21:46
  • Not the issue, but don't use `std::endl` unless you need the extra stuff that it does. `'\n'` ends a line. – Pete Becker Apr 04 '16 at 21:49
  • @RichardHodges No disrespect meant, but I'm not a fan of "You'll have to trust us on this" - I don't have a copy of the standard, but I've read what's available on cppreference.com (not sure how close that is). What this all says to me is there's a lack of a good answer in the standard that should be addressed. – bizaff Apr 04 '16 at 22:00
  • "but that seems to just hide what you're really trying to accomplish." Does it though? Is what you want to accomplish taking a pointer of one type and treating it as a pointer to another, is what you want to accomplish taking the bytes in an int and copying them into a float? – GManNickG Apr 04 '16 at 22:02
  • @bizaff no offense taken. Here's a link to a recent draft of the standard. have a search for "aliasing": http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4527.pdf – Richard Hodges Apr 04 '16 at 22:10
  • 1
    In my opinion, the standard should offer better support for this. If I memcpy, I throw away the type information and am on the hook for calling a standard library function correctly. That seems riskier than a reinterpret_cast, when the whole purpose of a reinterpret_cast is to "Convert[] between types by reinterpreting the underlying bit pattern" – bizaff Apr 04 '16 at 22:10
  • @bizaff: Not only that, but saying that a programmer who wants to use a pointer of type T1 to access something of type T2 can't do so in a way that a compiler would know can only access things of type T1 and T2, but must instead do so in a way that might alias anything, anywhere, whose address has been exposed to the outside world, does not seem like a recipe for performance. – supercat Apr 05 '16 at 17:47
  • Yup, it's pretty silly that it took ISO C++ until C++20 to introduce `std::bit_cast<>` https://en.cppreference.com/w/cpp/numeric/bit_cast which checks for same size and trivially copyable. As low-level languages, ISO C and C++ are disappointing here, although many compilers provide options like `gcc -fno-strict-aliasing`. – Peter Cordes Mar 22 '21 at 20:38

3 Answers3

9

Credit to Anton here please. His answer was first and his is correct.

I am posting this exposition because I know you won't believe him until you see the assembler:

Given:

#include <cstring>
#include <iostream>

// prevent the optimiser from eliding this function altogether
__attribute__((noinline))
float convert(int in)
{
    static_assert(sizeof(float) == sizeof(int), "Oops");
    float result;
    memcpy(&result, &in, sizeof(result));
    return result;
}

int main(int argc, char * argv[])
{
    int a = 0x3f800000;
    float f = convert(a);


    std::cout << a << std::endl;
    std::cout << f << std::endl;
}

result:

1065353216
1

compiled with -O2, here's the assembler output for the function convert, with some added comments for clarity:

#
# I'll give you £10 for every call to `memcpy` you can find...
#
__Z7converti:                           ## @_Z7converti
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
#
# here's the conversion - simply move the integer argument (edi)
# into the first float return register (xmm0)
#
    movd    %edi, %xmm0
    popq    %rbp
    retq
    .cfi_endproc
#
# did you see any memcpy's? 
# nope, didn't think so.
#

Just to drive the point home, here's the same function compiled with -O2 and -fomit-frame-pointer :

__Z7converti:                           ## @_Z7converti
    .cfi_startproc
## BB#0:
    movd    %edi, %xmm0
    retq
    .cfi_endproc

Remember, this function only exists because I added the attribute to prevent the compiler from inlining it. In reality, with optimisations enabled, the entire function will be optimised away. Those 3 lines of code in the function and the call at the call site will vanish.

Modern optimising compilers are awesome.

but what I really wanted was this std::cout << *reinterpret_cast<float *>(&a) << std::endl; and I think it expresses my intent perfectly well.

Well, yes it does. But c++ is designed with both correctness and performance in mind. Very often, the compiler would like to assume that two pointers or two references don't point to the same piece of memory. If it can do that, it can make all kinds of clever optimisations (usually involving not bothering make reads or writes which aren't necessary to produce the required effect). However, because a write to one pointer could affect the read from the other (if they really point at the same object), then in the interests of correctness, the compiler may not assume that the two objects are distinct, and it must perform every read and write you indicated in your code - just in case one write affects a subsequent read... unless the pointers point to different types. If they point to different types, the compiler is allowed to assume that they will never point to the same memory - this is the strict aliasing rule.

When you do this: *reinterpret_cast<float *>(&a),

you're trying to read the same memory via an int pointer and a float pointer. Because the pointers are of different types, the compiler will assume that they point to different memory addresses - even though in your mind they do not.

This is the struct aliasing rule. It's there to help programs perform quickly and correctly. A reinterpret cast like this prevents either.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Last note is the killer-important point here. A huge majority of the really neat-o tricks require the optimizer to be turned on. Don't examine the debug build or make profiling assumptions based on it. It's deliberately stupid to make debugging the logic easier. – user4581301 Apr 04 '16 at 22:11
  • @RichardHodges Don't get me wrong, I never disbelieved you, I'm comfortable in assembly, and I agree optimizers have come a LONG way.. but I'm still disappointed that memcpy is the "correct" standard-compliant answer to the question :) – bizaff Apr 04 '16 at 22:22
  • @bizaff :-) added some explanation re the strict aliasing rule and the reasons behind it. hope it's helpful. – Richard Hodges Apr 04 '16 at 22:32
  • What useful optimizations would be impeded by requiring that an expression like the above must be considered a potential access to *both* a `float` and to whatever the type of `a` was [though not to arbitrary other types]? I would think requiring a compiler to recognize those accesses would be far less harmful to performance than requiring that a program use `memcpy` [which a compiler may then have to assume might be capable of aliasing anything, anywhere, whose address has been exposed to the outside world]. – supercat Apr 05 '16 at 17:46
  • @supercat it is a precondition of memcpy that the source and destination regions do not overlap. This allows the compiler to know that there is guaranteed to be no aliasing. It is for this reason that memcpy is absolutely perfect for the job. Furthermore, as you can see above, memcpy is a 'magic' function which in this case the compiler simply removes. The job of the programmer is to perfectly express intent. When we do that, our compilers do beautiful things on our behalf. – Richard Hodges Apr 05 '16 at 17:53
  • @RichardHodges: The source and destination won't overlap each other, but unless the compiler knows where the source pointer "came from" it must ensure any pending writes to global variables are completed before the memcpy, and unless it knows where the destination came from it must discard any global variable values it may have cached and cannot move any global variable reads ahead of the memcpy. – supercat Apr 05 '16 at 18:01
  • @RichardHodges: Given `uint16_t *buff;`, a loop which calls `inline void store_pair(uint32_t dat) { *((uint32_t*)buff)=dat; buff+=2; }` will be efficient if the compiler recognizes that `buff` can't alias itself, but if the assignment were replaced with a `memcpy`, the compiler would have to store `buff` before each store and reload `buff` afterward--a severe performance penalty which could have been avoided with more usable aliasing rules. – supercat Apr 05 '16 at 18:10
  • You'll need to post some code that I can compile to check your thinking. The use of the cast you suggest is actually UB. – Richard Hodges Apr 05 '16 at 18:13
  • See http://godbolt.org/#compilers:!((compiler:g6,options:'-std%3Dc99+-x+c+-O2',sourcez:MQSwdgxgNgrgJgUwAQB4DOAXO4MDoAWAfAFCiSyKqYBO4A5gSTDgIwBsA%2BhkgFQAOAbmIA3APYg4STKOoIWACmZgMAZgBMXJHACGGAJTEA3sSS95inOq489fPQF4dGIab4Bqe2qEBfEeMkAttpgAJ7SsgpKqhrcPGjUEAbGpgBmMkjyOEgg9gAMAtkoLLn52W5uBqam4XLy8RAA2iAAuno%2BfhJSGDIIahbKVtxOSSZIAQgBEHwh8nwANABkTnMALG2j7p7tYp1BoTV9UYO89SOp6ZnK2XkFIEUlt%2BWVVQd1CU2t7UAA%3D)),filterAsm:(commentOnly:!t,directives:!t,labels:!t),version:3 [hopefully that works]. Compare the generated code for manystore1 and manystore2. – supercat Apr 05 '16 at 20:57
  • The cast-and-store is a concept which the Standard does not require compilers to support, but which is so useful and can be so easily supported that compilers almost universally supported it until it became more fashionable for compiler writers to say "since the Standard doesn't require us to support this concept we're not going to", thus making it impossible to express semantics like this in a manner which is both efficient and guaranteed to work. – supercat Apr 05 '16 at 21:00
  • @supercat avoiding chat - contact details and counter-code here :-) http : //tinyurl .com / hswtrh7 – Richard Hodges Apr 05 '16 at 22:33
  • @RichardHodges: That link looks like the version I posted. Maybe you posted the wrong url? – supercat Apr 06 '16 at 15:14
  • [btw, in case I didn't make it clear earlier--I would only expect compilers to support cast-and-store in case the pointer happened to be suitably aligned for the destination type, but in many cases code can ensure that, such as when storing pairs of items to a buffer from which they will be read one at a time, or vice versa]. – supercat Apr 06 '16 at 15:18
  • @supercat can't figure out how to make a short url to godbolt. The godbolt one doesnt fit here :/ I wrote a beautifully simply templated "big cast" forward iterator and passed it into std::copy - produced beautiful code :-) – Richard Hodges Apr 06 '16 at 17:22
  • @RichardHodges: I'm curious what problems you can see, aside from obtuseness on the part of compiler writers, with saying that the act of casting a T1* to a T2* should be recognized as notification that a particular pointer of nominal type T2 is about to be used to access something that might either be a T2 or a T1. The poorly-written language in the C Standard (I would consider the amount of confusion it has caused as prima facie evidence that it is poorly written) doesn't say that a T1* which is immediately cast to a T2* should, at least for a little while, be recognizable as aliasing... – supercat Apr 06 '16 at 17:38
  • ...either type, but the rationale for imposing the rule gives as its sole justifying example a piece of code where the compiler would have no particular reason to believe that a pointer of one type might identify an object of another. I would suggest that if code receives a short*, casts it to long*, and dereferences it, I would consider the fact that the pointer was received as type short* should be considered a good reason to believe that it might identify a "short", and the fact that it was cast to "long*" should be a considered a good reason to believe that it might identify a "long". – supercat Apr 06 '16 at 17:43
  • @supercat On reflection I agree with you. Perhaps it's time to submit an RFC for explicit_memory_cast<>()? – Richard Hodges Apr 06 '16 at 17:56
  • @RichardHodges: Adding new methods of casting could be helpful, especially if they could easily be adapted (though perhaps only with -fno-strict-alias), to existing code, though in some cases that wouldn't be quite sufficient to allow optimal code generation, since in some cases the necessary semantics would be "I am going to be accessing a thing of type X as type Y for awhile, and won't need to access it again as type X until I'm done." A compiler would then not have to worry about about accesses of type X* that are interspersed with accesses to that Y*, until after the code indicated... – supercat Apr 06 '16 at 18:17
  • ...that it was done with the Y* in question [which would mean that a compiler would have to ensure that code behaves as though all pending writes to Y* are performed before any future reads of X* that might identify the same object]. Perhaps what's needed is a "converting_pointer" type, whose construction would establish that all accesses of type X or Y [or all types, if X is void] that occur before its creation are visible to it, and whose destruction would ensure that any accesses done through it would be seen by any future accesses of type X or Y [or all types, if X is void]. – supercat Apr 06 '16 at 18:21
  • @supercat isn't this the case now for the special pointer to char or unsigned char? This is the only kind of pointer that may be legally aliased with another of a different type IIRC. – Richard Hodges Apr 06 '16 at 18:24
  • @RichardHodges: I'm mostly a C programmer rather than C++, but I'd like to see intrinsics whose functionality would be like those of the aformentioned converting_pointer, but with an additional proviso (perhaps also supportable in converting pointers) that construction or destruction could either reinterpret memory or fill it with unspecified values, thus allowing writes or reads that could be moved across the intrinsic to be omitted altogether. – supercat Apr 06 '16 at 18:25
  • @RichardHodges: The "character-type exception" to aliasing rules is an ugly hack which should have been considered "necessary but obsolescent" from the get-go, since it can greatly undermine the efficiency of code that actually needs to use character-type data. I see no particular reason to perpetuate that usage into new syntactical structures. – supercat Apr 06 '16 at 18:30
8

Use memcpy:

memcpy(&f2, &a, sizeof(float));

If you are worried about type safety and semantics, you can easily write a wrapper:

void convert(float& x, int a) {
    memcpy(&x, &a, sizeof(float));
}

And if you want, you can make this wrapper template to satisfy your needs.

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • 1
    This really seems like the wrong answer to me - not that you're wrong, but that the standard is very much lacking a good answer. It's actively hiding information from the compiler by stripping away types and working in char pointers. If it's the only current way to do things standards compliant, so be it. – bizaff Apr 04 '16 at 21:55
  • @bizaff I anticipated your disbelief, so have provided an answer containing a complete proof. – Richard Hodges Apr 04 '16 at 21:58
  • @bizaff: This is the correct answer. Is your intent not "take the bytes in an int and copy them into a float"? `memcpy` is the way to express this. Pointer aliasing is not. Usually people skip to pointer aliasing in the name of performance, but that's yet another premature optimization. – GManNickG Apr 04 '16 at 22:01
  • @GManNickG it's worse than premature optimisation. It's undefined behaviour. – Richard Hodges Apr 04 '16 at 22:02
  • @GManNickG My intent is to reinterpret the bit pattern stored in an int as float. A better example would have been `std::cout << *reinterpret_cast(&a) << std::endl;` – bizaff Apr 04 '16 at 22:17
  • @GManNickG Most of my work is in the bare-metal embedded space which is why I'm very interested in the standards compliant answer and disappointed in memcpy. In some of my projects, a memcpy is not an acceptable overhead compared to the reinterpret_cast - it's not a premature optimization, it's a necessary optimization. – bizaff Apr 04 '16 at 22:46
  • 1
    @bizaff as it has been shown, the compiler is able to optimize the calls to `memcpy`, so it behaves exactly like `reinterpret_cast`. – Anton Savin Apr 04 '16 at 22:48
  • @AntonSavin for this compiler, yes, but I can't trust that optimization works on all of them we use without testing them with varying optimization levels we use - I feel I'm coming across as argumentative which I'm not trying to do. I was just hoping for one standard answer to rule them all and while you provided the best existing answer, I was hoping there was a better one. – bizaff Apr 04 '16 at 23:00
  • 1
    @bizaff: Fundamentally, the Standard is broken but compiler writers used to recognize that the Standard should have allowed some things it didn't. The rationale behind all of the aliasing rules says that a compiler should have no reason to assume that a write made to using int* of unknown origin should affect a named variable of type "double" (probably a fair assumption), but the rule has been used to justify having compilers ignore aliasing even in cases where a pointer is cast and used in the same expression, where it should be obvious to anyone that's not being deliberately obtuse... – supercat Apr 05 '16 at 20:42
  • ...that the newly-cast pointer is likely to alias an object of the old type. The supposed purpose of the rule is to facilitate optimizations, but hyper-modern interpretations do the opposite by disallowing constructs where a sensible compiler could identify specific types that would likely be aliased, and instead requiring memcpy which can alias anything. – supercat Apr 05 '16 at 20:45
  • The C99 Standard specifies that `memcpy` and `memmove` convey to the destination the Effective Type of the source in some circumstances. Given that C++ says its `memcpy` and `memmove` work like the C equivalents, is there anything in the C++ Standards which specifies that C++ does not inherit that detestable behavior from C? – supercat Sep 02 '16 at 21:16
1

As you've found out, reinterpret_cast can't be used for type punning

Since C++20 you'll have a safe way for type punning via std::bit_cast

uint32_t bit_pattern = 0x3f800000U;
constexpr auto f = std::bit_cast<float>(bit_pattern);

At the moment std::bit_cast is only supported by MSVC

While waiting for others to implement that, if you're using Clang you can try __builtin_bit_cast. Just cast it like this

float f = __builtin_bit_cast(float, bit_pattern);

See demo on Godbolt


In other compilers or older C++ standards the only way you can do is via a memcpy

However many compilers have implementation-specific way to do type punning, or implementation-specific behavior regarding type punning. For example in GCC you can use __attribute__((__may_alias__))

union Float
{
    float __attribute__((__may_alias__)) f;
    uint32_t __attribute__((__may_alias__)) u;
};

uint32_t getFloatBits(float v)
{
    Float F;
    F.f = v;
    return F.u;
}

ICC and Clang also support that attribute. See demo

phuclv
  • 37,963
  • 15
  • 156
  • 475