В чем причина того, что стандарт C рассматривает константу рекурсивно?

9

Стандарт 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.

Я думаю, что было бы лучше, либо:

  1. Чтобы ограничить создание constнеструктур с constполями, и в таком случае выдать диагностику. Это позволило бы понять, что structсодержащиеся поля только для чтения сами по себе доступны только для чтения.
  2. Определить поведение в случае записи в constполе, принадлежащее constнеструктуре, чтобы сделать приведенный выше код (*) совместимым со стандартом.

В противном случае поведение не является последовательным и трудно понять.

Итак, в чем причина того, что C Standard constрекурсивно рассматривает -ness, как он выражается?

Михаил Панков
источник
Если честно, я не вижу там вопроса.
Барт ван Инген Шенау
@BartvanIngenSchenau отредактировал, чтобы добавить вопрос, заявленный в теме в конце тела
Михаил Панков
1
Почему отрицательный голос?
Михаил Панков

Ответы:

4

Итак, в чем причина того, что C Standard рассматривает константу рекурсивно, как она выражается?

Только с точки зрения типа, не делать этого было бы неправильно (другими словами: ужасно сломано и намеренно ненадежно).

И это из-за того, что "=" означает для структуры: это рекурсивное присваивание. Отсюда следует, что в конечном итоге у вас s1._a = <value>происходит "внутри правила набора текста". Если стандарт допускает это для «вложенных» constполей, он добавляет серьезное несоответствие в свое определение системы типов в качестве явного противоречия (может также отбросить constфункцию, поскольку она просто стала бесполезной и ненадежной по своему определению).

Ваше решение (1), насколько я понимаю, излишне заставляет всю структуру быть там, constгде есть одно из ее полей const. Таким образом, s1._b = bбыло бы недопустимо для неконстантного ._bполя в неконстантном поле, s1содержащем a const a.

Тиаго Сильва
источник
Хорошо. Cедва имеет систему звукового типа (больше похоже на кучу угловых коробок, привязанных друг к другу на протяжении многих лет). Кроме того, другой способ поставить задание к structэто memcpy(s_dest, s_src, sizeof(S_t)). И я уверен, что это действительно так. И в таком случае даже существующая «система типов» не запрещает вам делать это.
Михаил Панков
2
Очень верно. Я надеюсь, что я не имел в виду, что система типов C является надежной, только то, что намеренно делая конкретную семантику несостоятельной, сознательно побеждает ее. Более того, хотя система типов C не строго соблюдается, способы ее нарушения часто бывают явными (указатели, косвенный доступ, приведение типов) - даже несмотря на то, что ее эффекты часто неявны и их трудно отследить. Таким образом, наличие явных «заборов» для их нарушения лучше информирует, чем противоречие в самих определениях.
Тьяго Сильва
2

Причина в том, что поля только для чтения доступны только для чтения. Там нет большого сюрприза.

Вы ошибочно полагаете, что единственный эффект - это размещение в ПЗУ, что действительно невозможно при наличии смежных неконстантных полей. В действительности оптимизаторы могут предполагать, что constвыражения не записаны, и оптимизировать на основе этого. Конечно, это предположение не выполняется, когда существуют неконстантные псевдонимы.

Ваше решение (1) нарушает существующий законный и разумный кодекс. Этого не произойдет. Ваше решение (2) в значительной степени удаляет значение для constучастников. Хотя это не нарушит существующий код, похоже, ему не хватает обоснования.

MSalters
источник
Я на 90% уверен, что оптимизаторы могут не предполагать, что constполя не записаны, потому что вы всегда можете использовать memsetили memcpy, и это даже будет соответствовать Стандарту. (1) может быть реализовано как, по крайней мере, дополнительное предупреждение, включаемое флагом. (2) 'ы обоснование в том , что, ну, точно - нет никакого способа , компонент structможно считать незаписываемый , когда вся структура является перезаписываемой.
Михаил Панков
«Необязательная диагностика, определяемая флагом» будет уникальным требованием к Стандарту. Кроме того, установка флага будет по-прежнему нарушать существующий код, так что в действительности никто не будет беспокоиться о флаге, и это будет тупик. Что касается (2), то 6.3.2.1:1 указывает прямо противоположное: вся структура недоступна для записи, когда есть один компонент. Тем не менее, другие компоненты могут быть доступны для записи. Ср C ++, который определяет также operator=в терминах членов, и, следовательно, не определяет, operator=когда один член const. C и C ++ все еще совместимы здесь.
MSalters
@constantius - тот факт, что вы МОЖЕТЕ сделать что-то, чтобы сознательно обойти константу члена, НЕ является причиной для оптимизатора игнорировать эту константу. Вы МОЖЕТЕ отбросить константу внутри функции, что позволяет вам что-то менять. Но оптимизатору в контексте вызова все еще разрешено предполагать, что вы этого не сделаете. Constness полезна для программиста, но в некоторых случаях она также является хорошей отправной точкой для оптимизатора.
Майкл Кохне
Тогда почему структура без записи может быть перезаписана с помощью ie memcpy? Что касается других причин - хорошо, это наследие, но почему это было сделано таким образом, во-первых?
Михаил Панков
1
Я все еще задаюсь вопросом, memcpyправильный ли твой комментарий о . AFACIT Цитата Джона Боде в вашем другом вопросе верна: ваш код пишет в объект, удовлетворяющий const, и, следовательно, НЕ является стандартной жалобой, конец обсуждения.
MSalters