From: scs@adam.mit.edu (Steve Summit)
Subject: Re: Union-initialization portability
Organization: none, at the moment
Date: Fri, 2 Oct 1992 17:48:45 GMT

[Note: it has taken me a few minutes to compose this article,
during which time several more incomplete, misleading, and/or
incorrect "answers" to the question at hand may have appeared.
This article tells the whole story; accept no substitutes! :-)
Those with nimble fingers might see it twice, though, due to a
cancellation and a correction.]

In article <1aedrtINN774@gap.caltech.edu>, Martin Shepherd
(mcs@goblin.caltech.edu) writes:
> In the following example I make the first member of the union a void *
> pointer so that any pointer can be used to initialize the union.
> 
> struct Descriptor {
>   char *name;
>   VarType type;
>   union {
>     void *init;  /* The initialization field of the union */
>     int *iptr;
>     float *fptr;
>     char *cptr;
>   } valof;
> };
>
> ... [deleted code which initializes valof.init field,
> then accesses e.g. valof.iptr] ...
>
> My question: Should this work, given that one is not actually casting
> the void * pointer back to the required type, but simply accessing
> it through union'd members? (It does work on a sparc). If not, is
> there another way to achieve this...

You are quite right to suspect that the lack of a cast back to
the required type might be significant.  Your suspicion is lost on
the prolific Richard M. Hartman (hartman@ulogic.UUCP), who writes
in article <363@ulogic.UUCP>:

> There seems to be nothing wrong w/ your example ... you seem worried
> that you haven't "cast back" the pointer into it's proper type.  You
> have.  That is what a union does -- provides multiply-typed handles
> to the same data.

Yes, a union "provides multiply-typed handles to the same data,"
which ends up meaning that it does *not* "`cast back' the pointer
into its proper type."  When you use a union, you take
responsibility for keeping track of what kind of bit pattern is
stored.  If you write to one member of a union and then read from
another, no change of bits occurs, which is why an attempt to do
so yields behavior that is, in general, undefined.  (Contrariwise,
when you need to write unportable code which interprets some bits
as another type, without changing the bits, a union is often the
way to go.)

Now, before we go any further, if there is anyone out there who
is not aware that different pointer types (that is, pointers
which point to different types) can have different sizes and
representations, now would be an excellent time to learn that
fact.

Let us confine our attention to void pointers and int pointers.
If we write

	int i;
	void *p = &i;

, we generate a pointer-to-int (when we take the address of i),
and it is implicitly converted to a pointer-to-void.  If p were a
pointer type of anything other than pointer-to-void (including
pointer-to-char), an explicit cast would be required here -- it
is a special case for pointers-to-void that they convert
automatically when being assigned to or from pointers of other
types.  In any case, like all conversions (implicit or explicit),
the conversion may involve a change of representation [note 1].

Having performed this assignment, we may later say

	*(int *)p = 5;

, which will assign 5 to i.  The (int *) cast here is more than
just window dressing; it may perform some significant conversion
of the pointer-to-void, p, back into a pointer-to-int, so that
when it is indirected upon with the leading unary * operator, an
lvalue of type int results (to which the value 5 is assigned).

However, if we had said

	union	{
		void *vp;
		int *ip;
		} u;

	u.vp = &i;

	*u.ip = 5;

, we would have very distinctly circumvented the second
conversion of the pointer-to-void back to pointer-to-int.
(The first conversion, of pointer-to-int into pointer-to-void,
would happen as before in the line "u.vp = &i;".)  On a machine
with different sizes or representations of the types pointer-to-
void and pointer-to-int, the last statement could (and likely
would) fail [note 2].

We could rescue this second example only by changing the last
statement to

	*(int *)u.vp = 5;

, but in doing so we would not really be using the union at all;
the code ends up being essentially equivalent to the first example.

To return to Martin's original question, the problem is
(apparently) to build a little symbol table which can hold values
of various types.  Unions often prove useful in such cases, but
they are usually used to hold the values themselves, not pointers
to them.  Since this is clearly a long article already, I'll pull
in most of Martin's example code, changing it to use a union
which holds the values directly:

	typedef enum {FLOAT_VAL, INT_VAL, CHAR_VAL} VarType;

	struct Descriptor {
	  char *name;
	  VarType type;
	  union {
	    int ival;
	    float fval;
	    char cval;
	  } valof;
	};

	/* initialization deleted because it would be quite different */

	  for(i=0; i<NUM_VAR; i++) {
	    switch (test[i].type) {
	    case FLOAT_VAL:
	      printf("Variable %s=%f\n", test[i].name, test[i].valof.fval);
	      break;
	    case CHAR_VAL:
	      printf("Variable %s=%c\n", test[i].name, test[i].valof.cval);
	      break;
	    case INT_VAL:
	      printf("Variable %s=%d\n", test[i].name, test[i].valof.ival);
	      break;
	    }
	  }

However, if your symbol table must hold not values but pointers
off to data values stored elsewhere (in Martin's example there
were a few arrays full of values), it turns out that a simple
pointer-to-void will suffice -- you don't need a union at all.
Here is Martin's code again, rewritten without the union:

	typedef enum {FLOAT_PTR, INT_PTR, CHAR_PTR} VarType;

	struct Descriptor {
	  char *name;
	  VarType type;
	  void *ptr;
	};

	float float_array[20];
	char char_array[9];
	float fvar;

	#define NUM_VAR 3
	struct Descriptor test[NUM_VAR] = {
	 {"farray", FLOAT_PTR, &float_array[0] },
	 {"cvar",   CHAR_PTR,  &char_array[0]  },
	 {"fvar",   FLOAT_PTR, &fvar           }
	};

	  for(i=0; i<NUM_VAR; i++) {
	    switch (test[i].type) {
	    case FLOAT_PTR:
	      printf("Variable %s=%f\n", test[i].name, *(float *)test[i].ptr);
	      break;
	    case CHAR_PTR:
	      printf("Variable %s=%c\n", test[i].name, *(char *)test[i].ptr);
	      break;
	    case INT_PTR:
	      printf("Variable %s=%d\n", test[i].name, *(int *)test[i].ptr);
	      break;
	    }
	  }

Note the explicit casts as the pointers are dereferenced in the
printf statements.

Martin had one other idea:

> or should I use a function call
> such as:
> 
>   init_variable(&test[2], "fvar", FLOAT_PTR, &fvar);
> 
> prototyped as:
> init_variable(Descriptor *dsc, const char *name, VarType type, void *valof);
> 
> to cast the 'void *valof' pointer in its argument list to the correct type?

This would work, but it attacks the wrong end of the problem, the
one that, in fact, was already solved in the original code.
In the static initialization

	struct Descriptor test[NUM_VAR] = {
	 {"farray", FLOAT_PTR, {&float_array[0]} },
	 ...

, the pointer-to-float value derived from &float_array[0] is
implicitly converted into a pointer-to-void, since valof.init,
the first member of the union (and the only member which can be
statically initialized) is a pointer-to-void.

A function call such as

	init_variable(&test[2], "fvar", FLOAT_PTR, &fvar);

would be a fine way to fill in elements of the symbol table at
run time (it would have to be called with a prototype in scope,
so that the fourth argument would be again implicitly converted
into a pointer-to-void), but it doesn't do anything about getting
the (pointer) values back out of the union again (and, as Richard
correctly noted, a function call can't be used in static
initialization).

Speaking of static initialization, that's the other problem with
a conventional union which holds values (not pointers) of several
different types -- they're a real nuisance to initialize.

					Steve Summit
					scs@adam.mit.edu

Note 1.  For example, on a word-addressed machine, a pointer-to-
int might be a 16-bit quantity which can point to any four-byte
int in the machine's address space, while a pointer-to-void (and,
equivalently, pointer-to-char) might be an 18-bit quantity,
selecting a four-byte word and then one particular byte within
that word.

Note 2.  To fully appreciate how the code which puns pointer
types through a union could fail, consider (or test, if you're
into such things) the behavior of the code

	union	{
		int i;
		double d;
		} u;

	u.i = -1;

	printf("%f\n", u.d);

That this code fails (where by "fails" I mean doesn't print
anything meaningful) is not too surprising: floating-point values
are well known to be stored using bit patterns wildly different
from simple integers.  Yet the analogous situation holds for
pointer types, once we understand that different pointer types
may also have different representations.