Стандарт C99 говорит в 6.5.16: 2:
Оператор присваивания должен иметь модифицируемое lvalue в качестве своего левого операнда.
и в 6.3.2.1:1:
Модифицируемое lvalue - это lvalue, которое не имеет типа массива, не имеет неполного типа, не имеет типа с константой, и если это структура или объединение, не имеет какого-либо члена (включая рекурсивный любой член) или элемент всех содержащихся агрегатов или объединений) с типом, определенным const.
Теперь давайте рассмотрим не const
struct
с const
полем.
typedef struct S_s {
const int _a;
} S_t;
По умолчанию следующий код является неопределенным поведением (UB):
S_t s1;
S_t s2 = { ._a = 2 };
s1 = s2;
Семантическая проблема в этом заключается в том, что включающий объект ( struct
) должен считаться доступным для записи (не только для чтения), судя по объявленному типу объекта ( S_t s1
), но не должен считаться доступным для записи в формулировке стандарта (2 предложения на вершине) из-за const
поля _a
. Стандарт делает неясным для программиста, читающего код, что присвоение на самом деле является UB, потому что невозможно определить это без определения struct S_s ... S_t
типа.
Более того, доступ только для чтения к полю в любом случае обеспечивается только синтаксически. Нет никакого способа, которым некоторые const
поля не const
struct
будут действительно помещены в хранилище только для чтения. Но такая формулировка стандарта запрещает код, который намеренно отбрасывает const
спецификатор полей в процедурах доступа к этим полям, например, так ( это хорошая идея, чтобы сопоставить поля структуры в 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);
}
Так что, по какой-то причине, для целого, struct
чтобы быть только для чтения, достаточно объявить этоconst
const S_t s3;
Но для целого, struct
чтобы быть не только для чтения, недостаточно объявить это без const
.
Я думаю, что было бы лучше, либо:
- Чтобы ограничить создание
const
неструктур сconst
полями, и в таком случае выдать диагностику. Это позволило бы понять, чтоstruct
содержащиеся поля только для чтения сами по себе доступны только для чтения. - Определить поведение в случае записи в
const
поле, принадлежащееconst
неструктуре, чтобы сделать приведенный выше код (*) совместимым со стандартом.
В противном случае поведение не является последовательным и трудно понять.
Итак, в чем причина того, что C Standard const
рекурсивно рассматривает -ness, как он выражается?