The C99 standard says in 6.5.16:2:
An assignment operator shall have a modifiable lvalue as its left
operand.
and in 6.3.2.1:1:
A modifiable lvalue is an lvalue that does not have array type, does
not have an incomplete type, does not have a const-qualified type, and
if it is a structure or union, does not have any member (including,
recursively, any member or element of all contained aggregates or
unions) with a const-qualified type.
Now, let's consider a non-const
struct
with a const
field.
typedef struct S_s {
const int _a;
} S_t;
By standard, the following code is undefined behavior (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
The semantic problem with this is that the enclosing entity (struct
) should be considered writable (non-read-only), judging by the declared type of the entity (S_t s1
), but should not be considered writable by the wording of standard (the 2 clauses on the top) because of const
field _a
. The Standard makes it unclear for a programmer reading the code that the assignment is actually a UB, because it's impossible to tell that w/o the definition of struct S_s ... S_t
type.
Moreover, the read-only access to the field is only enforced syntactically anyway. There's no way some const
fields of non-const
struct
are going really be placed to read-only storage. But such wording of standard outlaws the code which deliberately casts away the const
qualifier of fields in accessor procedures of these fields, like so (Is it a good idea to const-qualify the fields of structure in C?):
(*)
#include <stdlib.h>
#include <stdio.h>
typedef struct S_s {
const int _a;
} S_t;
S_t *
create_S(void) {
return calloc(sizeof(S_t), 1);
}
void
destroy_S(S_t *s) {
free(s);
}
const int
get_S_a(const S_t *s) {
return s->_a;
}
void
set_S_a(S_t *s, const int a) {
int *a_p = (int *)&s->_a;
*a_p = a;
}
int
main(void) {
S_t s1;
// s1._a = 5; // Error
set_S_a(&s1, 5); // OK
S_t *s2 = create_S();
// s2->_a = 8; // Error
set_S_a(s2, 8); // OK
printf("s1.a == %d\n", get_S_a(&s1));
printf("s2->a == %d\n", get_S_a(s2));
destroy_S(s2);
}
So, for some reason, for an entire struct
to be read-only it's enough to declare it const
const S_t s3;
But for an entire struct
to be non-read-only it's not enough to declare it w/o const
.
What I think would be better, is either:
- To constrain the creation of non-
const
structures withconst
fields, and issue a diagnostic in such a case. That would make it clear that thestruct
containing read-only fields is read-only itself. - To define the behavior in case of write to a
const
field belonging to a non-const
struct as to make the code above (*) compliant to the Standard.
Otherwise the behavior is not consistent and hard to understand.
So, what's the reason for C Standard to consider const
-ness recursively, as it puts it?
Best Answer
From a type perspective alone, not doing so would be unsound (in other words: terribly broken and intentionally unreliable).
And that's because of what "=" means on a struct: it is a recursive assignment. It follows that eventually you have a
s1._a = <value>
happening "inside the typing rules". If the standard allows this for "nested"const
fields, its adding a serious inconsistency in its type system definition as an explicit contradiction (might as well throw theconst
feature away, as it just became useless and unreliable by its very definition).Your solution (1), as far as I understand it, is unnecessarily forcing the entire structure to be
const
whenever one of its fields isconst
. In this way,s1._b = b
would be illegal for a non-const._b
field on a non-consts1
containing aconst a
.