Prototyping Woes

Richard A. O'Keefe ok at goanna.cs.rmit.oz.au
Mon Mar 11 17:18:32 AEST 1991


I haven't seen any followups to this, so I'm doing it.
In article <1991Mar5.030826.15713 at telesys.cts.com>, kreed at telesys.cts.com (Kevin W. Reed) writes:
> I have a routine that can accept multiple parameters.
> I'm having a problem getting the compiler to accept a prototype
> so that it doesn't complain that I have "too many actual arguments".

An ANSI C prototype for a variadic function ***MUST*** have an ellipsis
"..." in it, and that must be the last thing in the prototype.  For
example, we can prototype printf() thus:
	int printf(char *, ...);

> pickit(int nargs, char *buffer, char *args)
>     {
> 	char **p;
> 	int  i,	l;
> 
> 	p = &args;
> 	l = 0;
> 
> 	/* First check that they are enough fields for the request */
			    ^^^^ "there"?
> 	
> 	for (i = strlen(buffer); i > 0; i--)
> 	    if (buffer[i-1] == '|')
> 		l++;
> 	if (l < nargs) return ERROR;
> 
> 	/* Now extract the fields */
> 	
> 	for (i = 0; i < nargs; i++) {
> 	    while (( *( *p )++ = *buffer++) ! = '|' );
> 	    *( *p - 1 ) = '\0';
> 	    p++;
> 	}
> 
> 	return TRUE;
> }

The function header here clearly and explicitly states that the
function has exactly three arguments, no more and no fewer.  There
is an implicit assumption here that pointer parameters are passed as
a contiguous block in ascending order with no gaps.  This assumption
simply isn't true on Pyramids, SPARCs, MIPS, and several other machines.
The code is not portable at all.

There are at least two ways to fix this.  One of them is to look up
"varargs" in an ANSI C manual or textbooks.  There are special macros
for variadic functions which MUST be used if you want portable code.

In this particular case, there isn't any real need to pass varying
numbers of arguments.  Pass *one* argument, an array of pointers.

	/*  split(string, delimiter, pointer_array, N)
	    is given a string, such as "foo|baz|ugh"
	    and a delimiter, such as '|'.
	    It parses the string, putting pointers to the fields
	    in the appropriate elements of pointer_array, and
	    replacing instances of the delimiter by NUL.
	    The result of the function is the number of fields
	    encountered.  If more than N are found, only the first
	    N fields will be stored and returned.  If fewer than
	    N are found, trailing fields are set to NULL (char*)0.
	    I have chosen to make "delim" an 'int' parameter so
	    that the function can be called without a prototype in
	    scope.  awk(1) programmers will recognise the function.
	    An easy generalisation would be to allow a set of delimiters.
	*/
	int split(char *source, int delim, char **fields, int N)
	    {
		register char *s;
		register int c;
		register int k;

		if (N <= 0) return 0;
		fields[0] = source;
		for (s = source, k = 1; (c = *s++) != '\0'; )
		    if (c == delim) {
			s[-1] = '\0';
			if (k == N) return k;
			fields[k++] = s;
		    }
		for (c = k; c < N; )
		    fields[c++] = (char*)0;
		return k;
	    }		

Now, to extract 3 fields from a string, we might do
	#define Max_String_Len /* whatever */
	#define N_fields 3
		char buffer[Max_String_Len+1];
		char *fields[N_fields];
		int field_count;

		field_count = split(
			strcpy(buffer, "field1*field2*field3"), '*',
			fields, N_fields);

It should be obvious how to adapt that to check for exactly N fields.
-- 
The purpose of advertising is to destroy the freedom of the market.



More information about the Comp.lang.c mailing list