[net.lang.c] Functions with union parameters

jss@sfjec.UUCP (J.S.Schwarz) (04/06/84)

To recap: The question concerns a function with an argument declared
to be
        union { ushort ch; char *p; }

edai!ok claims that this is not portable. But what is nonportable in
his(her?) examples is the suggested calls. The portable way to call a
function with such an argument is to declare a variable in the caller
of the same type and use it.

Thus:

    typedef union { ushort ch ; char *p; } UNION;

    callee( arg ) UNION arg ; { ... }

    caller() {
        UNION v ; v.ch = ... ; callee( v ) ; v.p = ... ; callee( v )
        ; .... }

This is fully portable provided your C compiler supports struct and
union arguments. (I realize that some don't.)

This points out the absence in C of any way to take a value and turn
it into a union except by assigning it to a variable of union type.

Edai!ok also asks if there is any guarantee that members of a union
all begin at offset 0.  K&R 8.5 says: "A union may be thought of as a
structure all of whose members begin at offset 0, ..."

Jerry Schwarz -- BTL, Summit -- sfjec!jss

P.S.: Since edai!ok hasn't seen the calls in the SIII kernel, I think
it is unreasonable to accuse the authors of writing non portable
code.

gwyn@brl-vgr.ARPA (Doug Gwyn ) (04/06/84)

I believe "lint" does warn about the non-portable struct/union kludges
in your examples.  However, if you were to use correct types for the
parameters in the first place, it would be perfectly portable (as I
believe Guy Harris has pointed out in another response).

A useful guideline is: with few exceptions, if one finds he is using
coercions (typecasts) heavily, he has chosen inappropriate data types.

It is true that some C implementations may have to resort to "thunks"
to pass struct/array parameters or function return values.  This can
be messy; for example, on PDP-11 UNIXes, one cannot use struct-valued
functions in the kernel since interrupts wreak havoc with the partially-
returned structs.  It is also usually much more efficient to pass
pointers to structs than the actual structs, as is well known.

ok@edai.UUCP (Richard O'Keefe) (04/10/84)

     A recent message in this newsgroup cited a fragment of the SIII tty
driver (I think that was it) as an example of a union without  a  struct
around  it.  Thank you Guy Harris for something I can show people to say
why DAI doesn't want SIII (:-)!  The fragment went something like

	foo(..., baz, ...)
	    ...
	    union {ushort ch; somethingelse *pt} baz;
	    {...}
	ushort c; somethingelse *p;
	    foo(..., c, ...)
	    foo(..., p, ...)

     Sorry folks, it's not portable.  A difficulty on some machines that
lack the VAX's nice address modes is that if you have big arrays in  the
stack, some of the local variables of a function can go beyond the range
of  base+displacement addressing.  SO, on such machines compiler writers
(legitimately!)  put array bodies onto a second stack and leave pointers
to them in the normal stack.  So long as they hide the fact that there's
actually a pointer variable there, nobody hurts.  But  records  can  get
pretty  big  too (e.g. the 4.1bsduser structure is 4k bytes according to
/usr/include/sys/param.h) and the same fix is  applied.   And  compilers
tend  to  treat unions as a special case of structs.  So when a function
has a struct or union (rather than a pointer to a struct or union) as  a
parameter,  it  may  look for it on the same stack as scalars (as in the
VAX C compilers), OR IT MAY LOOK FOR IT SOMEWHERE ELSE.  A program which
lint is happy with couldn't care less.

     A replacement which looks ok but isn't is
	typedef union {ushort ch; somethingelse *pt;} *hackp;
	foo(..., baz, ...)
	    hackp baz;
	    {... /* use *baz instead of baz */}
	...
	    foo(..., (hackp)&c, ...)
	    foo(..., (hackp)&p, ...)
Is there any guarantee that ch is at offset 0 in the  union?   If  there
is, fine, but if a compiler could align union fields on the *other* end,
it's trouble time, as baz->ch might mean say ((ushort*)(baz))[1].

     A clean method when there are just two alternatives like this is to
always pass two parameters, one of them a dummy.  E.g.
	foo(..., ch, pt, ...)
	    ushort ch;
	    somethingelse *pt;
	    {...}
	...
	    foo(..., c, (somethingelse*)0, ...)
	    foo(..., 0, p, ...)
The overhead is probably negligible, and if  there  is  an  "impossible"
value  for  one  of the arguments (in this case pt == NULL is probably a
good one) that can be the indication to use the other argument.

     Another hack you sometimes see,
	struct zz {int a,b;};
	foo(x)
	    struct zz x;
	    {...}
	... foo(1, 2) ...
suffers from the same problem, only worse,  as  you  have  alignment  to
worry about even when all arguments go on the same stack.

     Struct and union arguments have their uses, but a Lint  option  for
asking to be warned about them would be a Good Thing.