This is a multi-part question.
I've been trying to understand the C type system. First the C standard mentions the term "compatible type" a lot so I tried to understand that. The definition seems to be quite spread out, but from what I found:
6.2.7 Compatible type and composite type 1 Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators.55) Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types; if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier; and if one member of the pair is declared with a name, the other is declared with the same name. For two structures, corresponding members shall be declared in the same order. For two structures or unions, corresponding bit-fields shall have the same widths. For two enumerations, corresponding members shall have the same values.
REFS:
6.7.2 short == short int == signed short == signed short int, etc.
6.7.3
10) For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.
6.7.6
1.2)
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
2.6)
For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.
it appears to me that
So first, I'd like to ask if my interpretation is accurate.
Second, _Generic
selections in the standard are defined in terms of this notion of "compatible type":
6.5.1.1 Generic selection 2 A generic selection shall have no more than one default generic association. The type name in a generic association shall specify a complete object type other than a variably modified type. No two generic associations in the same generic selection shall specify compatible types. The controlling expression of a generic selection shall have type compatible with at most one of the types named in its generic association list. If a generic selection has no default generic association, its controlling expression shall have type compatible with exactly one of the types named in its generic association list.
but compilers appear to interpret it differently in regards to top level qualifiers:
$ $CC -x c -include stdio.h - <<<'int main(){puts( _Generic((int const){0}, int:"int", int const: "int const")); }' && ./a.out #int with gcc, and int const with clang
It seems to me the clang interpretation is correct, however what's perplexing is that
$ $CC -x c -include stdio.h - <<<'int main(){puts( _Generic((int const)0, int:"int", int const: "int const")); }' && ./a.out
says "int"
even on clang.
So my second question is what in the standard would be the basis for interpreting (int const)0
as of type int
and (int const){0}
as of type int const
?
Finally, in all of my compilers (tcc,gcc,clang) top level qualifiers appear to be ignored on all types in prototype type lists when determining compatibility between fucntions or function pointers:
for CC in tcc gcc clang; do echo CC=$CC; $CC -x c - <<<'int main(){ static void (*f)(int*), (*g)(int * restrict const volatile); f=g; }' ; done #no complaints
but I couldn't find any mention of this in the standard, so my final question is:
Is ignoring top level qualifiers on types in prototype type lists standard-sactioned in the context of determining function-compatibility?
Thanks.
The compatible type does not mean that they must be exactly the same type for all uses. Notice
struct foo *
in one translation unit is compatible with
struct foo *
if neither is a pointer to a complete type. However if the members of foo
are defined even after there declarations then the definitions must match, otherwise the prior pointers will not be compatible!
Likewise a type and its typedef
are compatible with each other.
But the compatibility does not require that the types are the same: an array can have an incomplete and a complete type and they're compatible with each other. In one translation unit you can declare
extern int a[];
and in another
int a[10];
and they're compatible types.
For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.
Furthermore a VLA type can be a compatible type with a statically-dimensioned array - it is considered compatible always, if the element type is the same, but the behaviour will be undefined if the dimensions do not actually match when they're required.
As for _Generic
, Clang is definitely at fault here. In fact this has been addressed in Defect report 481, and it was deemed that Clang is consistently wrong and GCC is right; The standard was amended for C18 as noted by ov2k. See also this Q/A for another case, this time arising from Clang not doing the lvalue conversion of an array to a pointer type.
(const int){0}
does create an lvalue of type const int
(a compound literal), but the subsequent lvalue conversion should drop the type qualifiers and the result should be int
. In fact the _Generic
selection should not be able to select any type that is const
-qualified, so I think compilers should issue a warning for even having the const
qualifier there.