Strange behavior of float in function definition. And declaration-definition mismatch, yet it works, how?
Asked Answered
F

4

5

How does the following code work even though the signature of the function in the declaration doesn't match with the definition? The function declaration has empty parameter list, yet the definition has one parameter. Why the compiler doesn't give error?

#include <stdio.h>
double f(); //function declaration
int main(void)  
{ 
   printf("%f\n", f(100.0)); 
}
double f(double param) //function definition
{
   return 5 * param ; 
}

It compiles and runs fine (ideone).

But if I change the type of the parameter in the definition, from double to float, it gives the following error (ideone):

prog.c:7: error: conflicting types for ‘f’
prog.c:8: note: an argument type that has a default promotion can’t match an empty parameter name list declaration
prog.c:2: error: previous declaration of ‘f’ was here

What is wrong with float? Why does it give error with float but not with double?

Here is the list of pairs of declaration and definition, along with which pair works, and which not:

  • Works (ideone)

    double f();              //declaration
    double f(double param);  //definition
    
  • Does not work (ideone)

    double f();              //declaration
    double f(float param);   //definition
    
  • Works (ideone)

    float f();               //declaration
    float f(double param);   //definition
    
  • Does not work (ideone)

    float f();               //declaration
    float f(float param);    //definition
    

So as it seems, whenever the parameter-type is float, it doesn't work!


So I've basically two questions:

  • Why does the first example work even though there is a mismatch in the declaration and the definition?
  • Why does it not work when the parameter-type is float?

I tried understanding the section §6.5.2.2 (C99), but the language is so cryptic that I couldn't clearly understand. I don't even know if I read the correct section. So please explain these behaviors in simple words.

Ferraro answered 5/6, 2011 at 16:1 Comment(0)
S
7

Your assumption that declaration does not match the definition is incorrect. (That would be the case in C++, but not in C). In C language the

double f();

declaration does not fully declare the function, i.e. it does not introduce the prototype. It only announces the fact that function f exists and that its return type is double. It says absolutely nothing about the number and the types of fs arguments. The arguments can be absolutely anything. In that sense the declaration in your example does match the definition (i.e. it doesn't contradict the definition, which is good enought for C compiler).

If you really wanted the declare a function that takes no arguments, you'd have to specify an explicit void in the parameter list

double f(void);

That would indeed contradict the definition. What you have originally does not.

When you call a function that has been declared with empty parameter list (), it is your responsibility to supply the proper number of arguments of proper type. If you make a mistake, the behavior is undefined. This is what the compiler warns you about when you change the actual parameter type to float.

Your analysis of "pairs" of declaration and definition is not entirely correct. It is misguided. It is not really about the declaration and definition. It is really about the definition and the way you call your function. In the original case you call it with a double argument and the function is declared with a double parameter. So everything is matching. But when you call it with a double argument and declare it with a float parameter, you get a mismatch.

Also note, that when a function is declared without a prototype, float arguments are always promoted to double arguments. For this reason, it is not possible to pass a float argument to a function declared with () parameter list. If you want to have float arguments, always use prototypes (the same applies to char and short arguments as well).

Sanitarium answered 5/6, 2011 at 16:14 Comment(0)
J
4

C allows the function declaration to be empty. From C99 6.7.5.3/14:

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.

This is distinct from a void parameter list, which is explicitly stating that the function has no arguments. From 6.7.5.3/10:

The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.

Note also that if the types aren't declared, then your declaration is not a prototype. From 6.2.1/2:

A function prototype is a declaration of a function that declares the types of its parameters.

The second question is indeed related to C99 6.5.2.2/6:

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.

So in your case, float gets promoted to double whenever the function is called, and a double is put on the call stack (or in eax or whatever). But of course, if your function definition takes a float, it would read a float off the call stack, which would lead to a binary incompatibility. The compiler knows this, hence the error message.

Japeth answered 5/6, 2011 at 16:8 Comment(2)
So I can't call f(float) passing double value? I think, double can be converted into float implicitly? Or am I missing something?Ferraro
@Nawaz: If the compiler knows that the function is f(float) at the call point, then yes you can (the double will be converted to a float, and a float will be put on the stack, or in eax or whatever). But if it doesn't know the argument types, then it will put a double on the stack. If your function really is f(float), then it will try to read a float from the stack (i.e. essentially a reinterpret-cast).Japeth
U
3

In C, an empty parameter list in the declaration means the function can be called with 0 or more arguments.

Such functions, when called with a floating-point number, implicitly take it as a double. (And integral parameters as int.)

So when you call foo(100.0), you are calling it with a double. If you try to call it with a float, the argument will be converted to double at the time of the call.

This will not work if you define the function to take a float, because the way doubles and floats are passed is different. Hence the compiler is helpfully giving you an error.

Be glad you made this mistake in 2011 and not 1985, because compilers used to be pretty stupid and this was a nightmarish kind of bug to track down.

Bottom line: It is very bad style to declare functions with empty parameter lists in modern C. Declare the function properly, and if it to be referenced by multiple translation units, put the declaration in a header file.

[edit]

As detly points out in a comment, if you actually want to declare a function to take zero arguments, declare it to take void. (Or switch to C++...)

Urdu answered 5/6, 2011 at 16:11 Comment(2)
If you need to declare a function that takes no arguments, use f(void).Invoke
Compilers weren't stupid (well they were, but that isn't the problem here): the language was stupid. This is simply how C worked prior to the ANSI standard. The behavior remains for backward compatibility. Don't use it. Use function prototypes.Insomnolence
I
1

In C, an empty function declaration is like using ... in C++. That is it matches any number and type of arguments. The problem with using a float instead of a double is that float automatically promotes to double. When calling f(...) (to borrow C++ notation), it doesn't know what type is expected, so it gets promoted to double. Later, when you redeclare f to take a float argument, that conflicts with the implicit declaration of f as f(double).

Insomnolence answered 5/6, 2011 at 16:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.