[comp.lang.c] use of varargs....portable???

lai@vedge.UUCP (David Lai) (04/08/88)

Perhaps this has been harped on before, but I seem to have discovered
some interesting differences between 2 C compilers (sun3 and sun4) that
make varargs not so portable.  If you know of any 'portable' varargs
implementations, please let me know.

The sun3 compiler:

	arguments are passed in a linear block of memory (on the stack)
	chars and shorts are widened to int, and narrowed* in the function
	structures are passed on the stack subject to:
		1) If the size of the structure is less than 4
		   then it will be padded on lower addresses
		   and passed as size 4, sizeof in the function
		   will not report the padding
		2) If the structure size is 4 or more, it will
		   be passed unpadded.
	floats are widedened to double, but not narrowed in the function
	everything else passed as is

* narrowed means the sizeof operator reports a the argument's size
  smaller than the argument's real stack usage.  This is important because
  the varargs macro must calculate the real stack usage of the argument.

The sun4 compiler:

	<I'm not sure how arguments are passed, but eventually they
	 appear in a linear block of memory>
	chars and shorts are widened to int, then narrowed
	structures are first copied into some temporary memory, then
	 passed as pointers.  Within the function sizeof shows the
	 actual structure size, not the pointer size.
	floats are widened to double, and not narrowed
	everything else is passed as is

Varargs usage:

	Sun3.  chars and shorts get by using va_arg(ap,int)
	       structures size>=4	     va_arg(ap,struct_type)
	       floats			     va_arg(ap,double)
	       structures size<4:

		declare a fake structure type (it will  be size 4):
			struct fake{ struct struct_type a; short pad;} tmp;
		get the item off the argument list
			tmp = va_arg(ap,struct fake);
		assign the first field to your variable
			str = tmp.a;

	       everything else use its real type  va_arg(ap,type)

	Sun4.  chars, shorts, floats as above
	       structures (nasty)
			str = *(va_arg(ap,struct_type *));
	       everything else		va_arg(ap,type)
-- 
The views expressed are those of the author, and not of Visual Edge or Usenet
David Lai (vedge!lai@oliver.cs.mcgill.edu || ...decvax!musocs!vedge!lai)

guy@gorodish.Sun.COM (Guy Harris) (04/10/88)

In article <206@vedge.UUCP>, lai@vedge.UUCP (David Lai) writes:
> Perhaps this has been harped on before, but I seem to have discovered
> some interesting differences between 2 C compilers (sun3 and sun4) that
> make varargs not so portable.  If you know of any 'portable' varargs
> implementations, please let me know.

How about Sun-3 and VAX?

The following program:

	#include <varargs.h>

	struct goober1 {
		char	c;
	};

	struct goober2 {
		int	i;
		int	j;
		int	k;
	};

	main()
	{
		struct goober1 goober1_1, goober1_2;
		struct goober2 goober2_1, goober2_2;

		goober1_1.c = 11;
		goober2_1.i = 1;
		goober2_1.j = 2;
		goober2_1.k = 3;
		goober1_2.c = 22;
		goober2_2.i = 100;
		goober2_2.j = 200;
		goober2_2.k = 300;
		varargs_func(2, goober1_1, 17, goober2_1, 34);
		varargs_func(3, goober1_1, 17, goober2_1, 34, goober1_2, 51);
		varargs_func(4, goober1_1, 17, goober2_1, 34, goober1_2, 51,
		    goober2_2, 68);
	}

	int
	varargs_func(va_alist)
	va_dcl
	{
		va_list ap;
		register int i;
		register int count;
		struct goober1 one;
		struct goober2 two;
		register int num;

		va_start(ap);
		count = va_arg(ap, int);
		printf("count = %d\n", count);
		for (i = 0; i < count; i++) {
			if (!(i&01)) {
				one = va_arg(ap, struct goober1);
				printf("goober1: c = %d\n", one.c);
			} else {
				two = va_arg(ap, struct goober2);
				printf("goober2: i = %d, j = %d, k = %d\n",
				    two.i, two.j, two.k);
			}
			num = va_arg(ap, int);
			printf("num = %d\n", num);
		}
	}

gives different answers on a VAX running 4.3BSD and a Sun-3 running SunOS
4.0 - *neither* of which is correct!  (A VAX running System V will probably
give the same wrong answer as a VAX running 4.3BSD; a VAX running VMS may very
well give a different answer.)

You see, on the VAX, "sizeof (struct goober1)" is 1, while on the Sun-3 (and, I
suspect, on most other 68K boxes), "sizeof (struct goober1)" is 2.  It appears,
however, that *both* compilers align arguments pushed onto the stack on 4-byte
boundaries, so that the value of "ap" should be bumped by 4 when fetching a
"struct goober1", not by 1 (as is done on the VAX) or 2 (as is done on the
Sun-3 and probably on other 68Ks).

Another way of putting this is "passing structures to varargs functions doesn't
work, in general".  Sad, but true.

It may be possible to fix some or all of these problems in current C; the
68K/VAX problem can be fixed by having the "va_arg" macro round the size of the
object up to a multiple of 4 bytes before adding it to "ap".

The problem of machines that don't pass structures simply by putting them
in-line after the other arguments (what do other machines that pass arguments
in registers, such as the MIPS or Pyramid, do here?) may require ANSI C to
solve it; if you declare a function as being a function taking a variable
number of arguments, e.g.

	#include <stdarg.h>

	...

	extern int varargs_func(int count, ...);

	...

	int
	varargs_func(int count, ...)
	{
		va_list ap;
		register int i;
		struct goober1 one;
		struct goober2 two;
		register int num;

		va_start(ap, count);
		...
	}

the compiler would know that a different calling sequence should be used and
just dump all the arguments past the last "fixed" argument into memory in the
form the <stdarg.h> implementation expects.

> The sun3 compiler:

	...

> 	structures are passed on the stack subject to:
> 		1) If the size of the structure is less than 4
> 		   then it will be padded on lower addresses
> 		   and passed as size 4, sizeof in the function
> 		   will not report the padding
> 		2) If the structure size is 4 or more, it will
> 		   be passed unpadded.

Note that, as mentioned above, "sizeof" does report whatever padding is made to
satisfy the compiler's general structure alignment rules, so on the Sun-3
structures are padded to a 2-byte boundary and that padding *is* reported by
"sizeof".  (Also note that "sizeof" should report the *same* size inside and
outside the function.)

> The sun4 compiler:

> 	<I'm not sure how arguments are passed, but eventually they
> 	 appear in a linear block of memory>

If you're not passing any floating-point parameters, the first 6 parameters are
passed in %o0-%o5; all subsequent parameters are passed on the stack.  The
SunOS 4.0 and Sys4-3.2L <varargs.h> includes

	#if defined(sparc)
	# define va_alist __builtin_va_alist
	#endif

and the appearance of "__builtin_va_alist" in the argument list tells the
compiler to generate code to dump arguments onto the stack in the proper place
for "va_arg" to pick them up.

> 	structures are first copied into some temporary memory, then
> 	 passed as pointers.  Within the function sizeof shows the
> 	 actual structure size, not the pointer size.

Which is as it should be; "sizeof (struct foo)" should always give the size of
such a structure.