What happens to the unspecified arguments in function()?

There is a difference between a function declaration and a function definition when there is an empty parameter list.

Section 6.7.6.3p14 of the C standard states:

An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. 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.

What this means is that this declaration:

void fun();

Means fun takes an unknown number of parameters. While this definition:

void fun()
{
    printf("What happened to those arguments?");
}

Means that fun takes no parameters. So this function call:

fun(12, 13.22, 1234567890987654321, "wow", 'c');

Is invalid and invoked undefined behavior because the number of parameters in the call don't match the actual number of parameters. This is spelled out in section 6.5.2.2p6 regarding the function call operator ():

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.

As for why this is allowed, it is legacy behavior that goes back to pre-standardized versions of C where the type of variables and the return type of functions defaulted to int and the method of declaring functions differed from what they are now.


The reason for supporting the notation is historical. Before the first C standard (C89/C90), you couldn't use prototypes in C; prototypes were one of the biggest and most important features of Standard C. All function declarations, therefore, were written the 'empty parentheses' style (when they were written at all; most functions that returned int were not declared at all). The type void was also added in C89/C90, though some compilers supported it before the standard was finalized.

Because it was crucial to the success of C89/C90 that existing code should mostly continue to work, the empty parentheses style had to be allowed by the standard. So, your code might have been written in pre-standard C as:

#include <stdio.h>

int fun();  /* This declaration would probably have been absent */

int main(void)
{
    fun(12, 13.22, 1234567, "wow", 'c');
    return 0;   /* This was required until C99 to give reliable exit status */
}

fun(i, d, l, s, c)      /* No return type - implicitly returns int */
long l;                 /* Defined out of sequence - bad style, but legal */
char c;                 /* Passed as int; converted to char in function */
char *s;                /* Should define all pointer arguments */
double d;               /* No definition of i; it was an int by default */
{
    printf("This is what happened to those arguments:\n");
    printf("i = %d\n", i);
    printf("d = %f\n", d);
    printf("l = %ld\n", l);
    printf("s = [%s]\n", s);
    printf("c = %c\n", c);
    /* No return statement - don't use the value from the function */
}

For the curious: you could omit the char *s; line in the function definition, and it still compiled and produced the same output. It was a bad idea to try that, though. You could replace the line int fun(); with static fun(); and the code compiles cleanly when no diagnostics are requested.

You get no warnings even now if you compile this file (old31.c) with GCC 9.3.0 using:

$ gcc -std=c90 -o old31 old31.c
$

Your example as written is skirting around the backwards compatibility provisions. Using void means it was new code (it would not have been valid in many pre-standard C compilers because it used void). And new code should not exploit the backwards-compatibility provisions without a good reason. That was true in 1991 as well as in the current millennium (but in 1991, there were a lot more good reasons to exploit the backwards-compatibility provisions). Good pre-standard code usually listed all parameters in the order they were used. Omitted definitions and out of sequence definitions were not entirely satisfactory.

You asked:

  • What can be done with those arguments?

In the code in the question, nothing can be done with the arguments. The caller pushes the values onto the stack, and pops them off when the function returns. The called function is unaware of their existence and can do nothing with them.

  • Is it possible to access those arguments within the function?

No — not using any standard mechanism.

Tags:

C

Function