Стандарт 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, как он выражается?
Ответы:
Только с точки зрения типа, не делать этого было бы неправильно (другими словами: ужасно сломано и намеренно ненадежно).
И это из-за того, что "=" означает для структуры: это рекурсивное присваивание. Отсюда следует, что в конечном итоге у вас
s1._a = <value>
происходит "внутри правила набора текста". Если стандарт допускает это для «вложенных»const
полей, он добавляет серьезное несоответствие в свое определение системы типов в качестве явного противоречия (может также отброситьconst
функцию, поскольку она просто стала бесполезной и ненадежной по своему определению).Ваше решение (1), насколько я понимаю, излишне заставляет всю структуру быть там,
const
где есть одно из ее полейconst
. Таким образом,s1._b = b
было бы недопустимо для неконстантного._b
поля в неконстантном поле,s1
содержащем aconst a
.источник
C
едва имеет систему звукового типа (больше похоже на кучу угловых коробок, привязанных друг к другу на протяжении многих лет). Кроме того, другой способ поставить задание кstruct
этоmemcpy(s_dest, s_src, sizeof(S_t))
. И я уверен, что это действительно так. И в таком случае даже существующая «система типов» не запрещает вам делать это.Причина в том, что поля только для чтения доступны только для чтения. Там нет большого сюрприза.
Вы ошибочно полагаете, что единственный эффект - это размещение в ПЗУ, что действительно невозможно при наличии смежных неконстантных полей. В действительности оптимизаторы могут предполагать, что
const
выражения не записаны, и оптимизировать на основе этого. Конечно, это предположение не выполняется, когда существуют неконстантные псевдонимы.Ваше решение (1) нарушает существующий законный и разумный кодекс. Этого не произойдет. Ваше решение (2) в значительной степени удаляет значение для
const
участников. Хотя это не нарушит существующий код, похоже, ему не хватает обоснования.источник
const
поля не записаны, потому что вы всегда можете использоватьmemset
илиmemcpy
, и это даже будет соответствовать Стандарту. (1) может быть реализовано как, по крайней мере, дополнительное предупреждение, включаемое флагом. (2) 'ы обоснование в том , что, ну, точно - нет никакого способа , компонентstruct
можно считать незаписываемый , когда вся структура является перезаписываемой.operator=
в терминах членов, и, следовательно, не определяет,operator=
когда один членconst
. C и C ++ все еще совместимы здесь.memcpy
? Что касается других причин - хорошо, это наследие, но почему это было сделано таким образом, во-первых?memcpy
правильный ли твой комментарий о . AFACIT Цитата Джона Боде в вашем другом вопросе верна: ваш код пишет в объект, удовлетворяющий const, и, следовательно, НЕ является стандартной жалобой, конец обсуждения.