With K&R C, and even more so with ISO C, it is possible for two declarations that refer to the same entity to be other than identical. The term “compatible type” is used in ISO C to denote those types that are “close enough”. This section describes compatible types as well as “composite types”—the result of combining two compatible types.
If a C program were only allowed to declare each object or function once, there would be no need for compatible types. Linkage, which allows two or more declarations to refer to the same entity, function prototypes, and separate compilation all need such a capability. Separate translation units (source files) have different rules for type compatibility from within a single translation unit.
Since each compilation probably looks at different source files, most of the rules for compatible types across separate compiles are structural in nature:
Matching scalar (integral, floating, and pointer) types must be compatible, as if they were in the same source file.
Matching structures, unions, and enums must have the same number of members. Each matching member must have a compatible type (in the separate compilation sense), including bit-field widths.
Matching structures must have the members in the same order. The order of union and enum members does not matter.
Matching enum members must have the same value.
An additional requirement is that the names of members, including the lack of names for unnamed members, match for structures, unions, and enums, but not necessarily their respective tags.
When two declarations in the same scope describe the same object or function, the two declarations must specify compatible types. These two types are then combined into a single composite type that is compatible with the first two. More about composite types later.
The compatible types are defined recursively. At the bottom are type specifier keywords. These are the rules that say that unsigned short is the same as unsigned short int, and that a type without type specifiers is the same as one with int. All other types are compatible only if the types from which they are derived are compatible. For example, two qualified types are compatible if the qualifiers, const and volatile, are identical, and the unqualified base types are compatible.
For two pointer types to be compatible, the types they point to must be compatible and the two pointers must be identically qualified. Recall that the qualifiers for a pointer are specified after the *, so that these two declarations
int *const cpi; int *volatile vpi;
declare two differently qualified pointers to the same type, int.
For two array types to be compatible, their element types must be compatible. If both array types have a specified size, they must match, that is, an incomplete array type (see 6.11 Incomplete Types) is compatible both with another incomplete array type and an array type with a specified size.
To make functions compatible, follow these rules:
For two function types to be compatible, their return types must be compatible. If either or both function types have prototypes, the rules are more complicated.
For two function types with prototypes to be compatible, they also must have the same number of parameters, including use of the ellipsis (…) notation, and the corresponding parameters must be parameter-compatible.
For an old-style function definition to be compatible with a function type with a prototype, the prototype parameters must not end with an ellipsis (…). Each of the prototype parameters must be parameter-compatible with the corresponding old-style parameter, after application of the default argument promotions.
For an old-style function declaration (not a definition) to be compatible with a function type with a prototype, the prototype parameters must not end with an ellipsis (…). All of the prototype parameters must have types that would be unaffected by the default argument promotions.
For two types to be parameter-compatible, the types must be compatible after the top-level qualifiers, if any, have been removed, and after a function or array type has been converted to the appropriate pointer type.
Another interesting note is that each enumeration type must be compatible with some integral type. For portable programs, this means that enumeration types are separate types. In general, the ISO C standard views them in that manner.
The construction of a composite type from two compatible types is also recursively defined. The ways compatible types can differ from each other are due either to incomplete arrays or to old-style function types. As such, the simplest description of the composite type is that it is the type compatible with both of the original types, including every available array size and every available parameter list from the original types.