The C99 standard says in 6.5.16:2:

An assignment operator shall have a modifiable lvalue as its left operand.

and in

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);

destroy_S(S_t *s) {

const int
get_S_a(const S_t *s) {
    return s->_a;

set_S_a(S_t *s, const int a) {
    int *a_p = (int *)&s->_a;
    *a_p = a;

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));


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:

  1. To constrain the creation of non-const structures with const fields, and issue a diagnostic in such a case. That would make it clear that the struct containing read-only fields is read-only itself.
  2. 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?

  • To be honest, I don't see a question in there. Dec 30 '13 at 9:58
  • @BartvanIngenSchenau edited to add the question stated in the topic in the end of the body Dec 30 '13 at 10:08
  • 1
    Why the downvote? Dec 30 '13 at 22:56

So, what's the reason for C Standard to consider const-ness recursively, as it puts it?

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 the const 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 is const. In this way, s1._b = b would be illegal for a non-const ._b field on a non-const s1 containing a const a.

  • Well. C barely has a sound type system (more like a bunch of corner cases strapped onto each other over the course of years). Besides, the other way to put the assignment to a struct is memcpy(s_dest, s_src, sizeof(S_t)). And I'm pretty sure it's the actual way it's implemented. And in such case even existing "type system" doesn't prohibit you doing that. Dec 30 '13 at 20:49
  • 2
    Very true. I hope I did not imply that C's type system is sound, only that deliberately making a specific semantics unsound deliberately defeats it. Moreover, though C's type system is not strongly enforced, the ways to breach it are often explicit (pointers, indirect access, casts) -- even though its effects are frequently implicit and hard to track. Thus, having explicit "fences" to breach them informs better than having a contradiction in the definitions themselves. Dec 30 '13 at 21:47

The reason is that read-only fields are read-only. No big surprise there.

You're mistakenly assuming that the only effect is on placement in ROM, which indeed is impossible when there are adjacent non-const fields. In reality, optimizers may assume const expressions are not written to, and optimize based on that. Of course that assumption doesn't hold when non-const aliases exist.

Your solution (1) breaks existing legal and reasonable code. That won't happen. Your solution (2) pretty much removes the meaning of const on members. While this won't break existing code, it seems to lack a rationale.

  • I'm 90% sure optimizers may not assume that const fields are not written to, because you always can use memset or memcpy, and that would even be compliant to the Standard. (1) can be implemented as, at the very least, additional warning, enabled by a flag. (2)'s rationale is that, well, exactly — there's no way a component of struct can be considered non-writable when the entire structure is writable. Dec 30 '13 at 17:27
  • An "optional diagnostic determined by a flag" would be a unique requirement for the Standard to demand. Besides, setting the flag would still break existing code, so in effect nobody would bother with the flag and it would be a dead end. As for (2), specifies exactly the opposite: the entire structure is non-writeable whenever one component is. However, other components may still be writeable. Cf. C++ which defines also operator= in terms of the members, and therefore doesn't define a operator= when one member is const. C and C++ are still compatible here.
    – MSalters
    Dec 30 '13 at 17:35
  • @constantius - The fact that you CAN do something to deliberately get around the constness of a member is NOT a reason for the optimizer to ignore that constness. You CAN cast away constness inside of a function, allowing you to change stuff. But the optimizer in the calling context is still allowed to assume you won't. Constness is useful for the programmer, but it's also a god-send for the optimizer in some cases. Dec 30 '13 at 20:50
  • Then why a non-writable structure is allowed to be overwritten with i.e. memcpy? As for other reasons — ok, it's legacy, but why was it done in such way in the first place? Dec 30 '13 at 20:51
  • 1
    I'm still wondering if your comment about memcpy is right. AFACIT John Bode's quote in your other question is right: your code writes to a const-qualified object and therefore is NOT standard complaint, end of discussion.
    – MSalters
    Dec 30 '13 at 21:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.