What's also interesting is the calling convention difference of functions with, and functions without a prototype. Consider an old style definition:
void f(a)
float a; {
/* ... */
}
In this case, the calling convention is that all arguments are promoted before being passed to the function. So f
receives a double
, but the parameter has type float
, which is perfectly valid. The compiler has to emit code that converts the double back to a float prior to executing the function's body.
If you include a prototype, the compiler does not do such automatic promotions anymore, and data is passed as being converted to the types of the parameters of the prototypes as if by assignment. So the following is not legal and results in undefined behavior:
void f(float a);
void f(a)
float a; {
}
In this case, the function's definition would convert the submitted parameter from double
(the promoted form) to float
because the definition is old-style. But the parameter was submitted as a float, because the function has a prototype. Your options of solving the contradictions are the two following
// option 1
void f(double a);
void f(a)
float a; {
}
// option 2
// this declaration can be put in a header, but is redundant in this case,
// since the definition exposes a prototype already if both appear in a
// TU prior to the call.
void f(float a);
void f(float a) {
}
Option 2 should be preferred if you have the choice, because it gets rid of the old style definition up front. If such contradicting function types for a function appears in the same translation unit, the compiler will usually tell you (but is not required). If such contradictions appear over multiple translation units, the error will go most possibly unnoticed and can result in hard to predict bugs. Best is to avoid these old style definitions.