Qualifiers (const
, volatile
, restricted
, and _Atomic
) apply only to lvalues (designators of objects in memory), not to values (data in expressions). Per C 2018 6.7.3 5:
The properties associated with qualified types are meaningful only for expressions that are lvalues.
Consider const int x = 3; int y = x;
. This is allowed. The const
is a restriction on x
. Once the value of x
is read, the result is just the int
3; there is no const
attached to the 3. Then we may set the value of y
to 3, and there is no requirement that y
not change after that. const
does not stick with the value, and the compiler will not warn us about this.
The declaration const int* const fun(const int *const ptr)
says the value returned by fun
is const
. That is meaningless, and a good compiler could warn that the const
just before fun
has no effect. Regardless, this qualifier is discarded when evaluating the function call, so there is no problem in assigning the return value of this function to a pointer that is not const
.
We can consider a simpler example:
int t;
const int * const x = &t;
const int * y = x;
The compiler does not warn about this because y
receives only the value of x
. The fact that x
may not change does not impede our ability to assign its value to y
, which can change.
Supplement
When an lvalue is used for its value in an expression, all its qualifiers are discarded. C 2018 6.3.2.1 2 says:
Except when it is the operand of the sizeof
operator, the unary &
operator, the ++
operator, the --
operator, or the left operand of the .
operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; …
Each qualifier says something about how an object in memory will be treated:
const
says the object in memory will not be modified (through this lvalue).
volatile
says the object in memory can change by means unknown to the C implementation or that accessing it can cause effects unknown to the C compiler.
restrict
says the object in memory will only be accessed by means derived from that particular lvalue.
_Atomic
says accesses to the object in memory behave as if it were one indivisible thing (not made of bytes that can be read or modified separately), so that accesses from multiple threads always use a “whole” value for it, never mixing partial accesses from different threads.