[comp.std.c] stdarg: va_list pass by value?

markhall@pyrps5.pyramid.com (Mark Hall) (11/30/90)

Consider the program below to print out two strings using stdarg.  I want
to know what should happen when you pass a va_list object as an argument
to a procedure which uses it to grab arguments.  Should the va_list
object have pass-by-value semantics, or pass-by-reference (ie, does the
"value" of the va_list object change)?


	#include <stdarg.h>
	string1(va_list ap)
	{
		printf("%s\n", va_arg(ap,char*)); 
	}
	string1_or_2(int dummy,...)
	{
		va_list ap;
		va_start(ap,dummy);
		string1(ap); /* print out "string1" */

			/* what will the next line print out?  
			 * string1 or string2? 
			 */
		printf("%s\n", va_arg(ap,char*));
		va_end(ap);
	}
	main()
	{
		string1_or_2(1,"string1", "string2");
	}

So what do you think?
	string1
	string2
or
	string1
	string1
I contend that every ANSI compiler in existence will emit the latter
output.  If the standard suggests that it should be the former, then
the tried-and-true method of passing around the address of the stack
as the va_list will not cut it.  If it should be the latter, then 
implementing varargs with non-trivial (ie, non-VAX) calling
conventions will require some ugly shenanigans.

Funny, I started posted this because I thought the first output
sequence was the "correct" one.  However, the more I look at it, 
the better the second output sequence looks.  If you want the
first sequence, you should be able to declare string1 differently 
and pass it in the *address* of the va_list:

	string1(va_list *ap)
	{
		printf("%s\n", va_arg(*ap, char*)); 
	}
	string1_or_2(int dummy,...)
	{
		va_list ap;
		va_start(ap,dummy);
		string1(&ap); /* pass in the address */
	...

Hmmm.  However, what about all those standard procedures (vfprintf,
_doprnt, etc) which chew up va_list objects (but which accept va_list
parms, NOT va_list* parms)?  Can someone set the record straight here?
Does the standard address this issue adequately?

-Mark Hall (smart mailer): markhall@pyrps5.pyramid.com
	   (uucp paths): {ames|decwrl|sun|seismo}!pyramid!markhall

markhall@pyrps5.pyramid.com (Mark Hall) (12/01/90)

In article <136152@pyramid.pyramid.com>, and in blissful ignorance, I wrote:

>Consider the program below to print out two strings using stdarg.  I want
>to know what should happen when you pass a va_list object as an argument
>to a procedure which uses it to grab arguments.  Should the va_list
>object have pass-by-value semantics, or pass-by-reference (ie, does the
>"value" of the va_list object change)?
>
>	#include <stdarg.h>
>	string1(va_list ap)
>	{
>		printf("%s\n", va_arg(ap,char*)); 
>	}
>	string1_or_2(int dummy,...)
>	{
>		va_list ap;
>		va_start(ap,dummy);
>		string1(ap); /* print out "string1" */
>
>			/* what will the next line print out?  
>			 * string1 or string2? 
>			 */
>		printf("%s\n", va_arg(ap,char*));
>		va_end(ap);
>	}
>	main()
>	{
>		string1_or_2(1,"string1", "string2");
>	}

I should have waited until I read the standard before shooting this off
(didn't have it at home last night).  It covers this very clearly:

	The object AP may be passed as an argument to another function;
	if that function invokes the VA_ARG macro with parameter AP,
	the value of AP in the calling function is indeterminate and
	shall be passed to the va_end macro prior to any further
	reference to AP.

The program above is non-conforming.  The output can go either way.

So. . . let me state the problem in another way.  Instead of passing AP
to a procedure, what if I just assign it to another AP?

	#include <stdarg.h>
	foo(int dummy, ...)
	{
		va_list ap1, ap2;
		char *cp1,*cp2;
		va_start(ap1,dummy);
		ap2 = ap1; 			/* will ap2 "track" ap1? */
		cp1 = va_arg(ap1,char *);
		cp2 = va_arg(ap2,char *);
		printf("%s %s\n", cp1, cp2);
	}
	main()
	{
		foo(1,"string1","string2");
	}

Here, will it print "sting1 string2" or "string1 string1"?  The
standard does not say what happens when you *assign* a va_list object
(as opposed to passing it as an argument).  Obviously, the standard
included the clause about passing the AP object because some architectures
cannot have va_list be a pointer to the call stack.  Some architectures
require a pointer to a structure or something which can hold some runtime
information.  Since the standard explicitly calls out the indeterminacy
of passing AP objects as arguments, shouldn't it also call out the 
indeterminacy of assigning AP objects?

-Mark Hall (smart mailer): markhall@pyrps5.pyramid.com
	   (uucp paths): {ames|decwrl|sun|seismo}!pyramid!markhall

gwyn@smoke.brl.mil (Doug Gwyn) (12/01/90)

In article <136152@pyramid.pyramid.com> markhall@pyrps5.pyramid.com (Mark Hall) writes:
>Should the va_list object have pass-by-value semantics, or
>pass-by-reference (ie, does the "value" of the va_list object change)?

This is intentionally not specified.  After your string1() function returns,
the value of ap in the string1_or_2() function is indeterminate; if you want
to continue using it in a strictly-conforming program you must invoke
va_end(ap) then another va_start(ap,dummy), which will reset the pointer
to the start of the variable argument list.

This was thrashed out in committee; please don't argue about it now.

gwyn@smoke.brl.mil (Doug Gwyn) (12/01/90)

In article <136196@pyramid.pyramid.com> markhall@pyrps5.pyramid.com (Mark Hall) writes:
>Since the standard explicitly calls out the indeterminacy
>of passing AP objects as arguments, shouldn't it also call out the 
>indeterminacy of assigning AP objects?

It doesn't have to; since va_list may be an array type, a strictly-
conforming program must not attempt to copy by in an assignment
expression.  This leaves open the question whether or not one could
memcpy() it, but my advice is to not try such elaborate hackery.

markhall@pyrps5.pyramid.com (Mark Hall) (12/04/90)

In article <14628@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>In article <136196@pyramid.pyramid.com> markhall@pyrps5.pyramid.com (Mark Hall) writes:
>>Since the standard explicitly calls out the indeterminacy
>>of passing AP objects as arguments, shouldn't it also call out the 
>>indeterminacy of assigning AP objects?
>
>It doesn't have to; since va_list may be an array type, a strictly-
>conforming program must not attempt to copy by in an assignment
>expression.  This leaves open the question whether or not one could
>memcpy() it, but my advice is to not try such elaborate hackery.

Aha!  That same argument could be applied to the indeterminacy of
passing the va_list as an argument.  From what you say, the user
should also have known that the va_list type could have been
an array, and so the state of the va_list object on return from
the function call would be indeterminate by direct implication.  Therefore
it is redundant for the standard to specifically call out the indeterminacy
of passing a va_list object as an argument.  So much for language-lawering 
the issue.

My personal objection is that, in the big scheme of things, there isn't
anything available to assist the poor programmer in writing a
"maximally portable program".  Had the standard called out a constraint
on the operations allowable on a va_list object (my tiny little case in
point), then I wouldn't (already!) be seeing so many non-conforming 3rd
party programs which were not "caught" by the compiler on which they
were developed.  Man, customers look at you funny when you tell them
their program isn't "conforming".  They tend to suspect that XYZ's
compiler is "better", even though your's did the harder job.  You don't
win marketing points by having the strictest compiler around.

-Mark Hall (smart mailer): markhall@pyrps5.pyramid.com
	   (uucp paths): {ames|decwrl|sun|seismo}!pyramid!markhall

gwyn@smoke.brl.mil (Doug Gwyn) (12/05/90)

In article <136434@pyramid.pyramid.com> markhall@pyrps5.pyramid.com (Mark Hall) writes:
>My personal objection is that, in the big scheme of things, there isn't
>anything available to assist the poor programmer in writing a
>"maximally portable program".  Had the standard called out a constraint
>on the operations allowable on a va_list object (my tiny little case in
>point), then I wouldn't (already!) be seeing so many non-conforming 3rd
>party programs which were not "caught" by the compiler on which they
>were developed.  Man, customers look at you funny when you tell them
>their program isn't "conforming".  They tend to suspect that XYZ's
>compiler is "better", even though your's did the harder job.  You don't
>win marketing points by having the strictest compiler around.

A deliberate decision was made to not attempt to turn the C standard
into a programming tutorial.  The examples were all supposed to simply
clarify technical points of the specification.  (There were some other
footnotes etc. added as the result of non-technical pressures, but the
resistance to doing so was pretty strong.)

There is at least one company that I know of who is marketing a "C
reference compiler" that is intended to check programs for strict
standard conformance.  Note that this is the opposite of a compiler
test suite.

The license granted implementors in the C standard is for the most
part intended to permit reasonable implementation choices to be made
for unusual environments.  Unless strict program conformance checking
is an explicit goal of your compiler product, you shouldn't use the
standard specifications as an excuse to arbitrarily reject programs
that could have been reasonably accepted; that would be contrary to
the "spirit of C", i.e. its intended use as a systems implementation
language (in addition to a platform for portable applications).