53

I have this piece of code:

int foo() { return 0; }
int main()
{
    int (*float_function)(float) = foo;
}

When compiled using x86-64 GCC 12.2, with -Wall, it produces the warning (Link):

warning: initialization of 'int (*)(float)' from incompatible pointer type 'int (*)()' [-Wincompatible-pointer-types]

But, when I change from float to double (Link):

int foo(){ return 0;}
int main()
{
    int (*double_function)(double) = foo;
}

The warning is now gone.

But I think that both of these should get a warning.

Am I wrong somewhere? Why does GCC not complain about the second example?

Rohan Bari
  • 7,482
  • 3
  • 14
  • 34
  • 5
    For background info, there is a nice discussion about function pointer compatibility here: https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type – nielsen Jan 21 '23 at 09:49
  • 1
    I knew the answer of this question (it's because of default argument promotions which are performed on calls to non prototyped functions) - but I didn't know for type compatibility between non prototyped and prototyped functions - so thanks for posting this question. – AnArrayOfFunctions Jan 22 '23 at 00:12
  • Related, if not a duplicate: [Compatibility of function types that does not include a prototype](https://stackoverflow.com/q/58353041/1187415) – Martin R Jan 22 '23 at 16:54

1 Answers1

59

int foo() is declared without specifying its parameters. This is an obsolescent feature that lets you call it with any arguments. When calling the function, integer arguments are promoted to int (if needed), and float arguments are promoted to double.

Due to this, it's impossible for this function to receive a float parameter, which makes it incompatible with int (*)(float), but not with int (*)(double).

If you want a function that takes no parameters, declare it as int foo(void) which will make it incompatible with both.

Note that even with double, the code is not valid C because int foo() {...} is a function definition, so the compiler knows that it has no parameters (see chux's comment below for the standard reference). Most compilers still allow it.

If you replace it with a declaration int foo(); and place the definition elsewhere then the above is correct. In that case, the relevant standard quote (C17 6.7.6.3/15) is:

For two function types to be compatible, [...] If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, [...] the type of each parameter shall be compatible with the type that results from the application of the default argument promotions.

interjay
  • 107,303
  • 21
  • 270
  • 254
  • 8
    Nice explanation. Having a function declared without specified parameters can often lead to confusion. It can be caught by enabling the warning [`-Wstrict-prototypes`](http://gcc.gnu.org/onlinedocs/gcc-4.4.0/gcc/Warning-Options.html) (no, `-Wall` does not enable all warnings). – nielsen Jan 21 '23 at 09:56
  • 3
    interjay, C11 § 6.7.6.3 14 has "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." contradicts. A function _definition_ as `int foo(){ return 0;}` should be the same as `int foo(void){ return 0;}`. I suspect the compiler used does not following this specification. – chux - Reinstate Monica Jan 21 '23 at 12:15
  • 1
    @chux-ReinstateMonica I think you're right and this shouldn't compile. But both gcc and clang do allow this, even in `-pedantic` mode. – interjay Jan 21 '23 at 12:39
  • The UB will only hit when the function is called. The C standard explicitly allows conversation to prototype-less function. Types **are** compatible. The compiler is too overzealous with this warning. – tstanisl Jan 21 '23 at 15:17
  • 2
    @tstanisl "the type of each parameter shall be compatible with the type that results from the application of the default argument promotions." - from the standard (6.7.6.3/15), regarding compatibility with a prototype-less function type. Besides, even if the standard did allow it, this is exactly the sort of thing that warnings are supposed to catch. – interjay Jan 21 '23 at 15:27
  • @interjay, Interesting. Definitively, you should include this citation in the answer. It precisely answers why `int(double)` and `int()` are compatible while `int(float)` and `int()` are not. Anyway, I've learned something today, thx. – tstanisl Jan 21 '23 at 20:41
  • 1
    "(no, -Wall does not enable all warnings)" [wat.](https://knowyourmeme.com/memes/wat) Is there some `-Wone-all-to-rule-them-all` which reliably catches all these foot guns? – Alexander Jan 22 '23 at 22:04