2

Using this answer is good because it is very portable, correct and passes compilers set to be strict, but it is less efficient than I want and than it could be, because it doesn't use the x86 bswap instruction. (Maybe other instruction sets have similar efficient instructions.)

If the system I'm running on supports ntohl() then I would expect ntohl() to use the bswap instruction and gets me close. ntohl() does exactly the right thing, but it only works on uint32_t, not on a float. Casting between a uint32_t and a float is type punning and not allowed by strict compilers. Making a union with a float and a uint32_t runs into undefined compiler behavior (per previous posts here).

My understanding from previous posts is that casting from any pointer type to a char * or vice versa is explicitly allowed. So what is wrong with this solution? I haven't seen it mentioned in any answers yet.

char NetworkOrderFloat[4]; // Assume it contains network-order float bytes

uint32_t HostOrderInt = ntohl(*(uint32_t *)NetworkOrderFloat);

char *Pointer = (char *)&HostOrderInt;

float HostOrderFloat = *(float *)Pointer;

The ideal solution here seems to be more environments supporting ntohf(), but that doesn't seem to have happened yet.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
lundblade
  • 91
  • 1
  • 3
  • https://stackoverflow.com/a/7396744/2702398 – oakad Apr 30 '18 at 02:57
  • “Making a union with a float and a uint32_t runs into undefined compiler behavior (per previous posts here)” not really, it's explicitly allowed by footnote 82 that was added in C99TC3 and preserved in later C standards. It is certainly **implementation-defined** behavior. – Pascal Cuoq Apr 30 '18 at 08:06
  • "Making a union with a float and a uint32_t runs into undefined compiler behavior (per previous posts here)" is not a problem with C as tagged here. That previous post is a C++ one and a union is a problem there. – chux - Reinstate Monica Apr 30 '18 at 12:35
  • What is the point being strictly portable when the floating-point representation is not guaranteed portable ? –  Apr 30 '18 at 13:26
  • @YvesDaoust: Code that has one constraint (implementation must use IEEE-754) is more readily usable than code that has two constraints (implementation must use IEEE-754 and must be little-endian). – Eric Postpischil Apr 30 '18 at 16:35

1 Answers1

3

Your proposal breaks “strict aliasing rules”, the first time when it does *(uint32_t *)NetworkOrderFloat. The expression (uint32_t *)NetworkOrderFloat is still the address of an array of chars, and accessing it with an lvalue of type uint32_t is against these rules. Details and more examples can be found in this article.

Using a union to convert a float's representation to uint32_t, on the other hand, is not forbidden by the C standard as far as I know. But you can always use memcpy if you worry that it is.

float NetworkOrderFloat = ...;
uint32_t tmp;
_Static_assert(sizeof(uint32_t)==sizeof(float),"unsupported arch");
memcpy(&tmp, &NetworkOrderFloat, sizeof(float));
tmp = ntohl(tmp);
memcpy(&HostOrderFloat, &tmp, sizeof(float));

A decent modern compiler should compile the memcpy calls to nothing and ntohl to bswap.

Netch
  • 4,171
  • 1
  • 19
  • 31
Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281