From §6.2.7.5 (page 66):
EXAMPLE Given the following two file scope declarations:
int f(int (*)(), double (*)[3]); int f(int (*)(char
I'm probably not the right person to answer this, but for what it is worth, here is the C99 rationale, which may be helpful:
6.2.7 Compatible type and composite type
The concepts of compatible type and composite type were introduced to allow C89 to discuss those situations in which type declarations need not be identical. These terms are especially useful in explaining the relationship between an incomplete type and a completed type. With the addition of variable length arrays (§6.7.5.2) in C99, array type compatibility was extended so that variable length arrays are compatible with both an array of known constant size and an array with an incomplete type.
Structure, union, or enumeration type declarations in two different translation units do not formally declare the same type, even if the text of these declarations come from the same header file, since the translation units are themselves disjoint. The Standard thus specifies additional compatibility rules for such types so that two such declarations are compatible if they are sufficiently similar.
QUIET CHANGE IN C99
Structures or union type declarations in different translation units now must have identical tags to be compatible.
I would intuitively understand the phrase "composite type" as meaning "structures and unions", which appears to be way off-target.
In the C language definition, arrays and structs are aggregate types (types composed of multiple elements). Unions are kind of their own animal, since they can only take on the value of one element at a time.
Composite types are more of an issue for compiler implementors, rather than us run-of-the-mill code monkeys. You and I would not attempt to define a composite type, or declare objects of that type.
In the example given, you have two file scope declarations for a function f
that are slightly different from each other. Based on the rules presented in 6.2.7/3, the compiler determines a type that works for both, such that it can enforce type semantics at compile time (i.e., any calls to f
can be properly checked, even with the slightly different declarations) and generate the proper machine code to call the function.