Reliable type-punning across C and C++ standards
Asked Answered
L

1

6

Is there a way to type-pun that is valid in both C and C++? Preferably low overhead, and avoiding trivial preprocessor hacks.

In C89, I know I can do something like this:

unsigned int float_bits(float num) {
    return *(unsigned int *)#
}

However this violates C99's strict aliasing rule. So something like this might be more portable across the various C standards:

unsigned int float_bits(float num) {
    union { float f; unsigned int i; } u;
    u.f = num;
    return u.i;
}

But I know that this is not valid C++, because only one member of a union can be “active” at a time. The typical solution given for both C and C++ is something like this:

unsigned int float_bits(float num) {
    unsigned int i;
    memcpy(&i, &num, sizeof(int));
    return i;
}

However, this relies on the compiler being able to optimize away the call to memcpy. Is memcpy the only method that is portable across C and C++ standards?

Leduc answered 2/11, 2019 at 19:33 Comment(12)
Hmmm, even the first (unsigned int *)&num is not reliable.Gemot
memcpy is the way to go here. Or just don't do type punning, it's rarely needed :)Tobi
Now if you post an answer that says memcpy is the only way, we can use this question as the canonical duplicate for all future questions of this type.Ghassan
@Tobi Why is memcpy any more (or less) reliable than using (unsigned int *)&num? (Both will fail if sizeof(float) != sizeof(int).)Externalize
@Adrian-ReinstateMonica re C++ only: even if sizeof(float) == sizeof(int) then *(unsigned int *)# is UB. memcpy is valid in this case and a static_assert can be used to check the sizes match.Ghassan
@Adrian-ReinstateMonica: * (unsigned int *) &num would violate C’s rule about aliasing: The compiler is entitled to assume that an access via an unsigned int * does not access an object that was defined as float, regardless of the casts. When it optimizes, code such as num = 4; * (unsigned int *) &num = 0; printf("%d\n", num); printf("%d\n", num); could print “4” and “0”, or “4” and “4”, or “0” and “0”, among other things.Bulldog
@RichardCritten I believe I've found a non-memcpy solution involving C-style casts to unsigned char *, which is an exception to the strict aliasing rule. If we would like to make this the canonical duplicate for all future questions, perhaps I could make a community-wiki answer with all of the known "alternatives" to type-punning, and their corresponding pros/cons?Leduc
@RyanAvella: You can read through your unsigned char*, but how do you get any other type of value out of it? It sounds like you’ll just end up implementing memcpy yourself.Brandes
@RyanAvella be aware that casting to a character type is allowed to access the bytes of some other type, but not the other way around. You can't get around the casting with an intermediate cast to char *Platinum
The first code is also wrong in C89 (which does have the strict aliasing rule)Masao
@curiousguy "effective type" is only relevant for malloc'd space which is not the case of this question. The effective type of a variable declared as T is always T regardless of memcpyingMasao
@M.M: There has, so far as I can tell, never been a consensus as to exactly when objects can take an effective type different from their declared type. In this particular case, they wouldn't, but if a function is passed the address of an object of static or automatic duration, it would have no way of knowing the declared type of that object even though it should have one.Inappreciative
K
7
unsigned int float_bits(float num) {
  unsigned int i;
  memcpy(&i, &num, sizeof(int));
  return i;
}

there is no side effect from that call of memcpy other than changing i to have the same bytes as num did.

It is true that compilers are free to insert a call to a library memcpy function here. They are also free to insert 1 million NOOPs, a pong simulation AI training session, and try to seach the proof space for the goldblach's conjecture proof.

At some point you have to presume your compiler isn't hostile.

Every decent compiler understands what memcpy does.

Knapsack answered 2/11, 2019 at 23:26 Comment(2)
in C: Does that change an effective type?Lavenialaver
@Lavenialaver No.Zounds

© 2022 - 2024 — McMap. All rights reserved.