Unspecified number of parameters in C functions - void foo()
Asked Answered
C

6

12

I read here that in C void foo() means a function foo taking an unspecified number of arguments of unspecified type.

Can anyone give me or point me to an example where a C function takes an unspecified number of arguments? What can this be applied to in C? I couldn't find anything on the web.

Chug answered 27/2, 2014 at 16:4 Comment(6)
first thing that comes to mind is printf..Telford
@tesseract, it uses ... in signature not an empty one like thisConversant
That declaration doesn't specify how many arguments foo() takes - the function might well take a specific number of arguments (and usually that is the case), however the compiler won't help you get that correct. With a declaration such as that, it's the programmer's responsibility to get the number of arguments (and their types) exactly right.Insuppressible
Here is a live example.Conversant
@Conversant ok, but how can you make the function take advantage of the fact that it was called by multiple different parameters? How can the function be aware of when it was called with 1 or 2 parameters?Chug
@JasonSwartz, The only you can do is to use the calling conventions and knowledge of the architecture you are running at and get parameters directly from the stack. Not sure you should use it IRL.Conversant
H
20

That's an old-style function declaration.

This declaration:

void foo();

declares that foo is a function returning void that takes an unspecified but fixed number and type(s) of arguments. It doesn't mean that calls with arbitrary arguments are valid; it means that the compiler can't diagnose incorrect calls with the wrong number or type of arguments.

Somewhere, perhaps in another translation unit (source file), there has to be a definition of the function, perhaps:

void foo(x, y)
long x;
double *y;
{
    /* ... */
}

This means that any call to foo that doesn't pass two arguments of type long and double* is invalid, and has undefined behavior.

Prior to the 1989 ANSI C standard, these were the only kind of function declaration and definition available in the language, and the burden of writing correct function calls was entirely on the programmer. ANSI C added prototypes, function declarations that specify the types of a function's parameters, which allow compile-time checking of function calls. (This feature was borrowed from early C++.) The modern equivalent of the above would be:

void foo(long x, double *y);

/* ... */

void foo(long x, double *y) {
    /* ... */
}

Old-style (non-prototype) declarations and definitions are still legal, but they're officially obsolescent, which means that, in principle, they could be removed from a future version of the language -- though since they're still around in the 2011 standard I don't know that that will ever actually happen.

There is no good reason to use old-style function declarations and definitions in modern C code. (I've seen arguments for using them in some corner cases, but I find them unconvincing.)

C also supports variadic functions like printf, which do take an arbitrary number of arguments, but that's a distinct feature. A variadic function must be declared with a prototype, which includes a trailing , .... (Calling a variadic function with no visible prototype isn't illegal, but it has undefined behavior.) The function itself uses macros defined in <stdarg.h> to process its parameters. As with old-style function declarations, there is no compile-time checking for arguments corresponding to the , ... (though some compilers may check some calls; for example gcc warns if the arguments in a printf call are inconsistent with the format string).

Hugh answered 27/2, 2014 at 16:17 Comment(7)
I could be wrong here, but gcc seems quite happy for me to use an old style declaration for a variadic function, I am not sure "A variadic function must be declared with a prototype" is correct.Wareroom
@Vality: That was a sloppy use of the word "must". Calling printf with no visible prototype, for example, has undefined behavior; it's an error, but the compiler isn't required to diagnose it.Hugh
@KeithThompson So is this a feature of the language or a bug? Is there a way to get the parameters from a void foo() function called with, for example, either 2 or 3 parameters? What is this feature used for?Chug
@JasonSwartz: Old-style non-prototype declarations are an obsolescent language feature, and they can't be used to write a function that can take either 2 or 3 arguments. Unless you need to deal with old code that uses them, just pretend they don't exist and always use prototypes.Hugh
I have used void someFunc() in both declaration and definition and never called it with parameters. Is it UB?Toname
@giorgi: void someFunc(); is an old-style non-prototype function declaration. It does not specify the number or types of the parameters. Similarly, void someFunc() { /* ... */ } is an old-style definition; it specifies that there are no parameters, but does not enforce it on callers. Old-style declarations and definitions are obsolescent, but are still fully supported by the language standard. A call that passes the correct number and types of arguments (none in this case) has well defined behavior. A call with incorrect arguments, like someFunc(42, "Oops!"), has undefined behavior.Hugh
As a matter of both style and safety, it's better to write void someFunc(void); and void someFunc(void) { /* ... */ }Hugh
W
3

This literally means that you are not telling the compiler what arguments the function takes, this means that it will not protect you from calling it with any arbitrary set of arguments. You would need to say in the definition precisely what arguments are actually taken for the function to be implemented however.

You could for example use this if you were generating a header file to describe a foreign function in external code, however you did not know what the function's signature actually was, it would still then be callable using your header but if you provide the wrong arguments in the call results are undefined.

Wareroom answered 27/2, 2014 at 16:17 Comment(0)
A
3

The C function calling standard allows for a function to be called with zero or more arguments and the number of arguments may or may not match the function interface.

The way this works is that it is up to the caller to adjust the stack after the called function returns rather than the called function adjusting the stack unlike other standards such as Pascal which require the called function to manage the stack adjustment properly.

Because the caller knows what arguments and their types have been pushed onto the stack before the called function is called and the called function does not, it is up to the caller to clear the pushed arguments from the stack after the called function returns.

With updated C standards, the function call interface description has become more complex in order to allow the compiler to detect and report interface problems that the original K&R C standard allowed to go undetected by the compiler.

The standard now is that you specify variable argument lists using elipsis notation of three periods or dots after the last known and specified argument in the called functions interface specification or declaration.

So you would see something like the following for some of the Standard C Library I/O functions:

 int sprintf (char *buffer, char *format, ...);

This indicates that the function sprintf requires that the first argument be a char pointer to a buffer, the second argument be a char pointer to a format string, and there may be other additional arguments. In this case any additional arguments would be what are needed to be inserted for the print format specifiers in the format string. If the format string is just a text string with no format specifies (something like %d for an integer for instance) then there would be no other arguments.

The newer C Standards specify a set of functions/macros for the use with variable argument lists, the varg functions. With these functions/macros the called function can step through the variable part of an argument list and process the arguments. These functions look something like the following:

int jFunc (int jj, char *form, ...)
{
   va_list myArgs;
   int     argOne;

   va_start (myArgs, form);
   argOne = va_arg (myArgs, int);
   va_end (myArgs);

   return 0;
}

The problem that we have with variable argument lists is that C does not have a way of communicating the variable argument or even how many arguments. So the designer of the function has to provide a mechanism. In the case of the C Standard Library I/O functions this is done with the format that indicates the number of arguments following the format string by specifying format specifiers for each argument. And since there is nothing that does a consistency check, you can end up with a format string that specifies more or less than the actual arguments resulting in either garbage output or less output than expected.

Since modern C compilers have some degree of backwards compatibility for old C source code, that means that you can use some of the older constructs and the compiler will allow it though hopefully with a warning.

The new function interface specifications are designed to reduce the chances of using a function incorrectly. So the new standards recommend that you use the function interface declaration so that the compiler can help you by detecting interface problems and incorrect variable usage in the function call.

However if you want to be a risk taker you do not have to use this safety net so if you like you can just define a function with an empty argument list and wing it.

You might also find an answer I put into this question about currying in C that uses variable argument lists along with a way to determine how many arguments are provided.

Allianora answered 27/2, 2014 at 16:26 Comment(0)
I
3

The declaration void foo(); is not necessarily the same thing as a function that takes a variable number of arguments.

All that it is is a function declaration that indicates nothing about the number of arguments the function takes (actually, this is not exactly true, see below about argument promotions). The declaration isn't a function prototype, which provides information about the number and types of the parameters (using an ellipsis to indicate variable length parameters).

The function definition (where the body of the function is provided) might take a fixed number of arguments, and might even have a prototype.

When such a function declaration is used, it is up to the programmer making a call to foo() to get the arguments that foo() expects (and their types) correct - the compiler has no information about the argument list foo() requires. It also restricts the type of parameters that the function definition can use because when the function is called without a prototype, therefore the default argument promotions will be applied to the arguments at the function call. So a function that's declared without a prototype can only expected to get promoted arguments.

See the following C99 standard sections for some details:

6.7.5.3/14 "Function declarators (including prototypes)

...

The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

...

6.5.2.2 Function calls

...

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined, except for the following cases:

  • one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;
  • both types are pointers to qualified or unqualified versions of a character type or void.
Insuppressible answered 27/2, 2014 at 16:36 Comment(0)
P
1

As of C23, old-style (K&R) function declarations and definitions have been removed from the C language.

This change was introduced by the proposal N2841: No function declarators without prototypes, which has the summay:

This removes the obsolescent support for function declarators without prototypes. The old syntax for function declarators without prototypes is instead given the C++ semantics.

Quoting the current C23 standard draft (N3096, Annex M.2 Fifth Edition):

Major changes in this fifth edition (__STDC_VERSION__ 202311L) include
...

  • mandated function declarations whose parameter list is empty be treated the same as a parameter list which only contain a single void;

Function declarations of the form void foo() are now exactly equivalent to void foo(void), just like in C++.

Principled answered 15/12, 2023 at 15:18 Comment(0)
C
0

As mentioned in many other answers, a function taking an unspecified number of arguments of unspecified type just means it's caller doesn't know about its definition. It's completely different from variadic functions where you must define with ... and then use va_list in stdarg.h to obtain the arguments

It's actually common in the past and there are many standard functions using that feature, for example open()/openat()/_open() with an optional last parameter

fd = open(filename, O_RDONLY);
fd = open(filename, O_CREAT | O_WRONLY,  0777);

See open() function parameters

main() is also a function that can receive varying number of arguments

int main();
int main(int argc, char **argv);
int main(int argc, char **argv, char **envp);
int main(int argc, char **argv, char **envp, char **apple);

In fact the Windows x64 calling convention is designed to support such unspecified number of arguments so it's a bit unfortunate for most modern usecases

Cowpuncher answered 31/5, 2021 at 16:44 Comment(1)
is there any reason for the dumb downvote?Cowpuncher

© 2022 - 2024 — McMap. All rights reserved.