Tech Tip: Lets Pretend

(How to fit a 'float' into a 'char[]'-shaped box... and get it out again!)

The C language provides a number of built-in data types (int, float, etc.) to make various operations easier to program. You can also define your own types using struct, and define functions to perform operations on them.

Note: typedef does NOT define a new type, merely a new name for an existing one. A future Tech Tip will discuss the definition of new types.

Sometimes, however, you need to interact with the Outside World, and look behind the scenes at one of these objects. A common requirement is to split a 32-bit integer into four 8-bit integers for transmission over a data link, and reconstruct it at the other end. This can be simply done using the built-in bitwise and arithmetic operations:

unsigned long old = 0x12345678;
unsigned char c[sizeof(long)];
unsigned long new;

/* split up */
c[0] = old & 0xff;
c[1] = (old >> 8) & 0xff;
c[2] = (old >> 16) & 0xff;
c[3] = (old >> 24) & 0xff;

/* use c as an array of size (sizeof(long)) */

/* reconstruct */
new = c[0] |
    (c[1] << 8) |
    (c[2] << 16) |
    (c[3] << 24);

This approach can't be used when you want to split up something that is not an integer - float, or a struct, for example. For these cases you would need to use one of several other methods, which include one of the only legitimate uses of a cast:

using_cast() {
        float old = 1.2345;
        unsigned char c[sizeof(float)];
        float new;

        int i;
        /* split up */
        for (i = 0; i < sizeof old; i++) {
            c[i] = ((unsigned char *) &old)[i];
        }

        /* use c as an array of size (sizeof(float)) */

        /* reconstruct */
        new = *((float *) c);
}

You will see that this sample code first

  • takes the address of old, which produces a float *
  • converts this float * to an unsigned char *
  • treats this unsigned char * as an array of unsigned char values

and then

  • uses the name of array c, which produces an unsigned char *
  • converts this unsigned char * to a float *
  • dereferences this pointer to give a float value.
using_union() {
        float old = 1.2345;
        union { float f; unsigned char c[sizeof(float)]; } u;
        float new;

        /* split up */
        u.f = old;

        /* use u.c as an array of size (sizeof(float)) */

        /* reconstruct */
        new = u.f;
}

#include <stdlib.h>

using_memcpy() {
        float old = 1.2345;
        unsigned char c[sizeof(float)];
        float new;

        int i;
        /* split up */
        memcpy(c, old, sizeof old);

        /* use c as an array of size (sizeof(float)) */

        /* reconstruct */
        memcpy(new, c, sizeof new);
}

This last example does almost the same thing as the first in this set of three, but uses the library memcpy() function instead of explicit casts and hand-coded copying. memcpy() and its companions treat whatever pointers they are handed as if they were arrays of unsigned char.

Be wary when handling float or double values in this manner: some combinations of bits in the IEEE-754 floating point format do not represent actual values and performing any calculations on them will cause Bad Things to happen. (A future Tech Tip will discuss floating point values and operations in more detail). These techniques are only valid if the unsigned char arrays are not modified between splitting up and reconstruction, and where they are handed between programs compiled with identical options by the same version of the compiler.


 
In these examples, we have used sizeof(type), or sizeof variable rather than an explicit 4 or 3 - we have delegated to the compiler the task of knowing precisely what size the objects are. The first example, using long, only cares about the low-order 32 bits of the long - while this is certainly sufficient for any of our compilers at the moment, and is the maximum size guaranteed by the language standard, it is possible that some future version might provide a larger long, and this code would fail to handle any extra bits. The later examples have no such limitation, and could also be used with any other type in place of float.
   

 

September 2005.