This is explicitly allowed by a special case in the rules for function parameter lists. N1570 §6.7.6.3p131 says:
In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.
But you must also understand that the "unqualified version" of a type like const char *
is still const char *
, because the type const char *
is derived from the type const char
, and §6.2.5p26 says
A derived type is not qualified by the qualifiers (if any) of the type from which it is derived.
That means that the declaration
void foo (const int x);
is compatible with the definition
void foo (int x) { ... }
but the declaration
void bar (const char *x)
is not compatible with the definition
void foo (char *x) { ... }
You might be wondering why these rules are the way they are. The short version is that in C, all arguments are always passed by copying the value (but not any data pointed to by the value, if there are pointers involved), so it doesn't matter whether an actual argument is const T
; the callee receives it as a regular old T
regardless. But if you copy a pointer to constant data, the copy still points to constant data, so it does matter and that qualifier should be preserved.
1 Document N1570 is the closest approximation to the 2011 ISO C standard that is publicly available at no charge.
To the best of my knowledge, these rules have not changed significantly since the original 1989 standard. Pre-C89 "K&R" C didn't have prototypes, nor did it have const
, so the entire question would be moot.