A function declaration serves as a hint to the compiler that a particular function exists. The function itself might be defined either in a different compilation unit or later on in the same compilation unit. The latter was especially useful in the old days where parsing was expensive and you wanted to avoid to parse a file several times. Let us have a look at an example:
bar which is defined after
If a compiler starts parsing the file from top to bottom, then as soon as code should be emitted for the expression
bar(42) it is unclear how that code should look like.
bar expect as a first parameter a
int, or even a
long—in other words what size should the first parameter have?
Function Declarators to the Rescue?
tl;dr A function declarator which is not in prototype-format is still problematic
A function declarator defines a function name and may define information about the types and numbers of the corresponding function parameters. A function declaration consists of a function declarator and a return type. Likewise, a function definition consists of a function declarator, a return type, and additionally the body of the function.
That means in a function declaration and definition a declarator may omit information about the types and numbers of parameters. Only a declarator which is in prototype-format contains information about the number and types of parameters—excluding parameters which are given via trailing ellipses. Thus the following combinations are possible:
Declaration w/o Prototype
A function declaration without a prototype only introduces the name of a function and its return type. Nothing is known about the arguments of the function:
This is indicated by an empty argument list. Note this does not define a nullary function. We will see below how to define a nullary function.
Since nothing is known about the arguments the compiler cannot warn if the function is called with an inappropriate number or types of arguments—which results in undefined behavior.
Declaration with Prototype
A function declaration with a prototype introduces in addition to the name and return type of a function (like a function declaration without a prototype does), also the number and types of all arguments (of course, excluding variadic arguments given via ellipses …):
Hence, a function prototype is in contrast to a function declaration without a prototype more strict in the sense that it also requires the number and types of all arguments. This enables a compiler to warn if a function is called with a wrong number of arguments or with a wrong argument type.
Observe a nullary function is given by the single argument
void (in contrast to C++ where no
void argument is required).
Definition w/o Prototype
A function definition without a prototype consists of the return type, name and body of the function:
Such functions are essentially nullary functions.
We will later on see why such function definitions without a prototype are still problematic and where the C standard is not stringent.
Definition with Prototype
A function definition including a prototype introduces the return type, name, the number and types of all arguments of a function.
Note, whenever a function has at least one argument, then a function definition includes a prototype.
Good Old Days
Observe that we did not take care of the notation as introduced by Kernighan and Ritchie, i.e., we excluded functions of the form
where you could even omit type declarations which default to
In the example the return type and the type of the first argument
argc of the function
main default to type
Despite this in K&R notation it is allowed to make use of an identifier list and a separate parameter type list which is very uncommon these days.
Hence we ignore this kind of notation.
Function Definition Without a (Prior) Prototype
If no prototype is available for a called function, then the compiler cannot check if the number and types of all arguments are compatible with the function. Hence the compiler has to assume that the code is correct as it is written down. Consider the following example:
The declaration and definition of function
foo do not contain a prototype, respectively.
In such a case the C11 standard assumes that the function is a nullary function.
C11 § 188.8.131.52 Function declarators (including prototypes) ¶ 14
The C standard is at that point vague.
the function has no parameters mean?
Does this mean that the type of function
foo is compatible with type
typedef void f(void);?
Does the function definition also define a prototype which is used in the subsequent lines of the very same compilation unit?
For the latter question, let us check what GCC outputs.
If compiled via GCC 8.1.1 using the options -Wall -Wextra -pedantic -std=c11 no warning or error is raised, whereas, Clang 6.0.0 outputs the following warning:
test.c:6:11: warning: too many arguments in call to 'foo' foo(42); ~~~ ^ 1 warning generated.
Since GCC does not even output a warning and Clang only outputs a warning, I assume that the standard is to read that no prototype is given in the function definition.
A function definition can, however, include a prototype as shown in the following:
In line one a function named
foo is declared with return type
Since no arguments are specified, the call to the function at line two is somewhat fine, i.e., at this point in time the best the compiler can do is to assume that the call is correct.
At line three function
foo is finally defined and also a prototype is given implicitly by the definition.
From this point on to the end of the current compilation unit, the prototype is in scope.
Therefore, at line four the compiler detects a misuse of function
foo since it is called with a parameter whereas the prototype specifies that the function has no arguments at all.
Hence, a compile time error is raised.
C11 § 6.9.1 Function definitions ¶ 7
Function Definition With a Prototype
So everything is fine as long as a prototype is given whenever a function is called? Well, yes and no. You are on the save side, if the code compiles. But there is a quirk which is surprising, if the code does not compile. Consider the following code:
All three functions come with a prototype at their definition site, respectively.
baz have in addition a prior function declaration without a prototype.
bar compile whereas function
baz does not, i.e., a compile time error is raised.
Why? Long story short the problem here is the combination of an argument type which gets promoted and a function declaration without a prototype.
Lets get through this piece by piece. Whenever a function is called for which no prototype is available, then all arguments get promoted according to C11 section 184.108.40.206 paragraph 6:
C11 § 220.127.116.11 Function calls ¶ 6
Lets illustrate the problematic part by an example:
baz gets called no prototype is available. Therefore, the argument gets promoted from type
However, typically both types are not compatible and they differ in size (
float: 32 bit,
double: 64 bit usually).
Hence at this point no valid code can be omitted and the compiler must return with an error in order to be sound.
Lets get back to function
baz of the previous example.
Prior the function definition a function declaration without a prototype is given.
In such a case the C11 standard requires that each type of an argument of the function definition is compatible to its corresponding type after the default argument promotions.
C11 § 18.104.22.168 Function declarators (including prototypes) ¶ 15
tl;dr Types are important, make use of them
A prototype defines how many arguments a function expects and the type of each argument (again module variadic arguments). This enables a compiler to check if the arguments of a called function are compatible or not. By that a lot of errors/bugs get visible at compile time. Hence the takeaway message is: Every declarator should contain a prototype.
The standard is even pretty clear about the use of a declarator without a prototype:
C11 § 6.11.6 Function declarators
The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.
To make sure that a prototype is always included, GCC has an option -Wstrict-prototypes such that a warning is raised if a prototype is missing (the option is neither implied by -Wall nor by -Wextra).