Path: utzoo!news-server.csri.toronto.edu!cs.utexas.edu!uunet!munnari.oz.au!goanna!ok From: ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) Newsgroups: comp.lang.c Subject: Re: Prototyping Woes Message-ID: <4935@goanna.cs.rmit.oz.au> Date: 11 Mar 91 07:18:32 GMT References: <1991Mar5.030826.15713@telesys.cts.com> Distribution: comp Organization: Comp Sci, RMIT, Melbourne, Australia Lines: 100 I haven't seen any followups to this, so I'm doing it. In article <1991Mar5.030826.15713@telesys.cts.com>, kreed@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.