Search code examples
clanguage-lawyerfunction-call

Compatibility of function types that does not include a prototype


There is a rule for function type compatibility N2310 6.7.6.3(p15):

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 parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions.

I can imagine an example:

#include <stdio.h>

int foo();

float bar();

int main(void){
    printf("%d\n", foo(1, 3)); //fine, int is unchanged by default argument promotion
    printf("%f\n", bar(1.0f, 2.0f)); //error, float is promoted to double
}

int foo(int a, int b){
    return a + b;
}

float bar(float b, float c){
    return b + c;
}

The thing that I found contradictory was that 6.5.2.2(p6) mentions that :

If the number of arguments does not equal the number of parameters, the behavior is undefined.

In the case of int foo() it has an empty identifier-list. So does a call printf("%d\n", foo(1, 3)); yield UB (2 arguments were supplied)?

Anyway the rules look pretty strange and kind of unnatural. What was the reason for that? I suppose some backward compatibility with previous versions of the Standard... ?


Solution

  • C 2018 6.7.6.3 15 tells you whether two types are compatible. So it can be used to compare two declarations. For foo, you have:

    int foo(); int foo(int a, int b) {...}

    Of these, the second has a parameter list, and the first is specified by a function declaration that is not part of a function definition and that contains an empty identifier list. So the rule is 6.7.6.3 15 applies. It says the parameter list shall not have an ellipsis terminator (and it does not), and that the type of each parameter shall be compatible with the type that results from the default argument promotions (and they are, since int produces int).

    Then, for bar, we have:

    float bar();
    float bar(float b, float c) {...}
    

    Again, 6.7.6.3 15 applies. But in this case, each parameter has a type that does not result from the default argument promotions, since the default promotions convert float to double. So these two declarations declare bar with incompatible types.

    Regarding 6.5.2.2 6:

    … If the number of arguments does not equal the number of parameters, the behavior is undefined…

    This refers to the number of parameters of the actual function, not the number of parameters appearing in the (empty) list in the declaration.

    Anyway the rules look pretty strange and kind of unnatural. What was the reason for that? I suppose some backward compatibility with previous versions of the Standard... ?

    Yes, C was originally lax about function declarations, allowing functions to be declared with empty parameter lists, if I recall correctly, all arguments were passed with the promoted types. Support for stricter and more precise declarations came later, and the rules are written to allow old code to continue to work.

    Note that the rule about compatibility of function types is relevant for the declarations of the functions, but not the calls.

    When a function call is being analyzed by the compiler, the rules in 6.5.2.2 are used to prepare the call. These rules say the arguments are treated in various ways according to the declaration of the function that is visible at the point of the call. (Technically, to the type of the expression that denotes the called function. This is often a function name but could be a pointer to a function, including one computed by a cast expression.)

    The rules about compatibility assure you that, if you call a function using a type that is compatible with the type of the actual function definition, then the call has defined behavior.