[comp.std.c] Is va_list defined by <stdio.h>?

gnu@hoptoad.uucp (John Gilmore) (04/06/91)

In an ANSI C implementation, it appears that the type "va_list" must be
defined by <stdio.h> because it it used to declare the arguments for
vfprintf, vprintf, and vsprintf.  However, it is not in the list of
types declared by <stdio.h>, either in Appendix C.10 (page 192) or
in section 4.9.1 (page 125).

The sections that document these particular vXXX functions say, e.g.:

	#include <stdarg.h>
	#include <stdio.h>
	int vsprintf(char *s, const char *format, va_list arg);

but it is not possible for <stdio.h> to declare these functions without
also declaring va_list, since some people will include <stdio.h> without
including <stdarg.h>.  <stdio.h> could avoid declaring these functions
if <stdarg.h> was not also included, but the includes could occur in either
order, making things very messy, and I don't think that's what the standards
committee had in mind.

Is this a real omission, or am I missing something?
-- 
John Gilmore   {sun,uunet,pyramid}!hoptoad!gnu   gnu@toad.com   gnu@cygnus.com
*  Truth :  the most deadly weapon ever discovered by humanity. Capable of   *
*  destroying entire perceptual sets, cultures, and realities. Outlawed by   *
*  all governments everywhere. Possession is normally punishable by death.   *
*      ..{amdahl|decwrl|octopus|pyramid|ucbvax}!avsd!childers@tycho          *

torek@elf.ee.lbl.gov (Chris Torek) (04/06/91)

In article <16863@hoptoad.uucp> gnu@hoptoad.uucp (John Gilmore) writes:
>In an ANSI C implementation, it appears that the type "va_list" must be
>defined by <stdio.h> ...

No; in fact, the type `va_list' must not be defined there, but must be
*used* there regardless (as you note).

This leaves the problem of somehow using a type without first defining
it.  There is a way, based on the observation that `typedef' does not
define a new type, but rather defines a new name for an existing type.
What one does, then, is this:

 A. Define some underlying representation V for `va_list'.  This is
    machine dependent, but is typically something like `pointer to
    char' or `pointer to struct __va_list'.

 B. In <stdio.h>, declare v*printf with something like:

	int vprintf(const char *fmt, struct __va_list *);

 C. in <stdarg.h>, define the type `va_list' with something like

	typedef struct __va_list *va_list;

This introduces two new problems: <stdio.h> is now machine dependent
(an otherwise-unnecessary situation), and <stdarg.h> and <stdio.h> must
somehow be kept in sync.

What we have done in the current BSD system, which solves all of this,
is create a machine-dependent header file (found in <machine/ansi.h>,
which is a minor misnomer) that reads more or less as follows:

	/* faux MIPS <machine/ansi.h> */

	#ifndef _ANSI_H_
	#define _ANSI_H_

	struct __va_list { int n0; char *p0, *p1; };	/* 2 save areas */

	#define	_CLOCK_T_	unsigned long
	#define	_PTRDIFF_T_	int
	#define	_SIZE_T_	unsigned int
	#define	_TIME_T_	unsigned int
	#define	_VA_LIST_	struct __va_list *
	#define	_WCHAR_T_	unsigned short

	#endif /* _ANSI_H_ */

This file is included whenever any of the machine-dependent types is
required.  Typedefs that appear in more than one standard header,
such as size_t (which appears in <stddef.h>, <stdio.h>, <string.h>,
and <time.h>), are defined in each standard header with the sequence

	#ifdef	_SIZE_T_
	typedef _SIZE_T_ size_t;
	#undef	_SIZE_T_
	#endif

so that each such typedef appears at most once.  Typedefs that appear in
only one header (namely va_list) are simply defined via

	typedef _VA_LIST_ va_list;

and <stdio.h> just uses the sequence

	#include <machine/ansi.h>
	int vprintf(const char *, _VA_LIST_);

(had va_list to appear in more than one standard header, we would have
had to create two implementation-space macro names for it; fortunately
this is not the case).

This `backwards' sequence (of defining the types via macros, then
turning those into typedefs exactly once by undefining the macro in the
process) uses the minimal amount of verbiage.  Another scheme is to
have one macro for each type, and another for each `typedef has been
done' flag, but this is unnecessary.

Machine-dependent headers such as <stdarg.h> are in fact read from
the `machine' directory as well.

Note that the technique used in SunOS 4.1 and 4.1.1, in which
<sys/stdtypes.h> contains all the `typedef's for all standard headers,
is incorrect, precisely because of the problem addressed here (some
headers must use some types which those same headers must not define).

(Good grief, these one-sentence paragraphs are starting to look like
Bill-Joy-ese. :-)  I find all the passive voice annoying too. [Look
what happens when you get wheedled into editing man pages....])
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov

gwyn@smoke.brl.mil (Doug Gwyn) (04/06/91)

In article <16863@hoptoad.uucp> gnu@hoptoad.uucp (John Gilmore) writes:
>In an ANSI C implementation, it appears that the type "va_list" must be
>defined by <stdio.h> ...

NO!  To the contrary, a conforming implementation must NOT define va_list
as a side effect of inclusion of <stdio.h>.

>but it is not possible for <stdio.h> to declare these functions without
>also declaring va_list, ...

False.

gnu@hoptoad.uucp (John Gilmore) (04/10/91)

Thanks, Chris, for your useful response.  My confusion lie in the fact
that __VA_LIST__ is not #undef'd after use in <stdarg.h>, like all the
other names defined in <machine/ansi.h>.  I had a case where the user
had included

	#include <stdarg.h>
	#include <stdio.h>

and the compile was failing because __VA_LIST__ was undefined in stdio.h.

Either the conventions for the use of each name in <machine/ansi.h>
should be documented, or this unusual usage of __VA_LIST__ should be
changed to match all the rest, and perhaps a new __VA_LIST_STICKY__
introduced for <stdio.h> to depend on.

torek@elf.ee.lbl.gov (Chris Torek) wrote:
> This introduces two new problems: <stdio.h> is now machine dependent
> (an otherwise-unnecessary situation), and <stdarg.h> and <stdio.h> must
> somehow be kept in sync.

I believe that ANSI C should not have forced us to introduce these two
new problems.  In other words, this is a botch in the standard, that
should be fixed in its next revision.  Preferably the fix will simply
permit <stdio.h> to define va_list, or indeed, for it to include
<stdarg.h>, so that no backdoor monkeyshines at all would be needed.
-- 
John Gilmore   {sun,uunet,pyramid}!hoptoad!gnu   gnu@toad.com   gnu@cygnus.com
*  Truth :  the most deadly weapon ever discovered by humanity. Capable of   *
*  destroying entire perceptual sets, cultures, and realities. Outlawed by   *
*  all governments everywhere. Possession is normally punishable by death.   *
*      ..{amdahl|decwrl|octopus|pyramid|ucbvax}!avsd!childers@tycho          *

gwyn@smoke.brl.mil (Doug Gwyn) (04/10/91)

In article <16965@hoptoad.uucp> gnu@hoptoad.uucp (John Gilmore) writes:
>I believe that ANSI C should not have forced us to introduce these two
>new problems.  In other words, this is a botch in the standard, that
>should be fixed in its next revision.  Preferably the fix will simply
>permit <stdio.h> to define va_list, or indeed, for it to include
><stdarg.h>, so that no backdoor monkeyshines at all would be needed.

Many of the top C experts in the world disagreed with you.
I think the standard has this exactly right.
However, it does look like 4.4BSD has an awkward implementation.
That's not the fault of the standard.

bhoughto@bishop.intel.com (Blair P. Houghton) (04/11/91)

In article <15781@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>Many of the top C experts in the world disagreed with you.
>I think the standard has this exactly right.

Is there a good reason why v*printf() aren't declared in <stdarg.h>?

The only one I can think of is an issue of categorization
that seems to have overwhelmed one of usage.

How could you use v*printf() without <stdarg.h>?

				--Blair
				  "I'm still avoiding varargs by
				   proper top-down design..."

torek@elf.ee.lbl.gov (Chris Torek) (04/11/91)

>In article <16965@hoptoad.uucp> gnu@hoptoad.uucp (John Gilmore) writes:
>>I believe that ANSI C should not have forced us to introduce these two
>>new problems.  In other words, this is a botch in the standard, that
>>should be fixed in its next revision.  Preferably the fix will simply
>>permit <stdio.h> to define va_list, or indeed, for it to include
>><stdarg.h>, so that no backdoor monkeyshines at all would be needed.

In article <15781@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>Many of the top C experts in the world disagreed with you.
>I think the standard has this exactly right.

The standard tries to make things easy for users, and makes things hard
for implementors in the process.  Whether this is a Good Thing depends
on whether you are a user or an implementor (and on whether you can get
your job done using only the facilities provided in X3.159-1989, or
whether you must use additional types, object, and functions).

>However, it does look like 4.4BSD has an awkward implementation.
>That's not the fault of the standard.

Indeed?  And how do *you* propose to implement <stdio.h> such that it
is machine independent, yet meets all the constraints imposed by both
ANSI and POSIX?  (If you want to make stdio.h machine dependent, my
reply is unprintable.)  Remember that the goal is to have 4.4BSD run
on:

	vax
	tahoe
	hp9000/300 (68020/030)
	intel 80386 (in as little as 640k!)
	mips (both big- and little-endian, i.e., decstations too)
	sparc

and anything else we can trick anyone into doing :-) (HP-PA and 88000
would be nice).  All of these are to be built from a single source.  We
are not there yet (only the first four have ever been integrated
together in one place, and some of the systems are not yet running),
but even four disparate platforms is tricky.

(Note that when I say `from the same source', I mean it: you can
NFS-mount /usr/src read-only, log on to all the different machine
types, and run `cd /usr/src; make' on all of them simultaneously.  Then
you can take bets on which one will finish first....)  (The binaries
are put in per-machine directories in /usr/obj, via symlinks.)
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov

steve@taumet.com (Stephen Clamage) (04/12/91)

bhoughto@bishop.intel.com (Blair P. Houghton) writes:

>In article <15781@smoke.brl.mil> gwyn@smoke.brl.mil (Doug Gwyn) writes:
>>Many of the top C experts in the world disagreed with you.
>>I think the standard has this exactly right.

>Is there a good reason why v*printf() aren't declared in <stdarg.h>?

>The only one I can think of is an issue of categorization
>that seems to have overwhelmed one of usage.

>How could you use v*printf() without <stdarg.h>?

v*printf() aren't declared in <stdarg.h> because you would need to
declare FILE in it, and this would blow the modularity -- you would have
a type and functions declared whenever you used stdargs, even if you did
not want standard I/O.  Remember, if you do not include <stdio.h> FILE
and v*printf are not reserved for use as local identifiers.

You cannot USE v*printf() without including <stdarg.h>, but you can
DECLARE them.  Thus:
1.  You may include <stdio.h> without <stdarg.h> if you do not need
    v*printf().
2.  You may include <stdarg.h> without <stdio.h> if you do not need 
    standard I/O.
3.  You must include both headers if you want to use v*printf().

This seems like entirely reasonable modularity to me.  You pay for
what you want to use.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

gwyn@smoke.brl.mil (Doug Gwyn) (04/16/91)

In article <11982@dog.ee.lbl.gov> torek@elf.ee.lbl.gov (Chris Torek) writes:
>Indeed?  And how do *you* propose to implement <stdio.h> such that it
>is machine independent, yet meets all the constraints imposed by both
>ANSI and POSIX?

The obvious solution is:
	/* stdio.h: */
	#include <sys/config.h>	/* defines __* types (only) */
	extern int vprintf(const char *,__va_list);
	/* etc. */

	/* stdarg.h: */
	#include <sys/config.h>	/* defines __* types (only) */
	#define	va_list	__va_list
	/* etc. */

	/* sys/config.h: */
	#ifndef __CONFIG_H_INCLUDED
	#define	__CONFIG_H_INCLUDED
	typedef char *__va_list;	/* or whatever is required */
	/* other typedefs here */
	#endif

I believe some implementors are using one such typedef-file per
standard typedef, but I would suggest having just ONE header file
that requires proper configuration when porting the implementation.
(You could try to automatically tailor this configuration file, but
I personally would prefer to edit it manually so that I could be
sure of the choices being made.)

This may not be too far from what your <machine.h> does.  You need
to be careful not to introduce spurious nonreserved identifiers into
the standard headers, which is why I show only __* names in the
system-configuration header.

gwyn@smoke.brl.mil (Doug Gwyn) (04/16/91)

In article <3769@inews.intel.com> bhoughto@bishop.intel.com (Blair P. Houghton) writes:
>Is there a good reason why v*printf() aren't declared in <stdarg.h>?

Yes, because that would have been counter to existing practice,
whereby they are declared by <stdio.h>.