У чому причина стандарту C вважати констресність рекурсивно?


9

Стандарт C99 говорить в 6.5.16: 2:

Оператор призначення повинен мати змінне значення як його лівий операнд.

та в 6.3.2.1:

Зміна 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класифікатор полів у процедурах аксесуарів цих полів, як-от так ( чи гарна ідея 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 редагував, щоб додати запитання, викладені в темі, в кінці тексту
Michael Pankov

1
Чому потік?
Михайло Паньков

Відповіді:


4

Отже, що є причиною того, що C Standard вважає стійкість рекурсивно, як це випливає?

З точки зору типу, не робити цього було б недоброзичливо (іншими словами: жахливо зламане і навмисно ненадійне).

І це через те, що означає "=" на структурі: це рекурсивне призначення. Звідси випливає, що з часом у вас s1._a = <value>відбувається "всередині правил набору тексту". Якщо стандарт дозволяє це робити для "вкладених" constполів, це додасть серйозну невідповідність визначенню системи типу як явне протиріччя (може також викинути constфункцію, оскільки вона просто стала марною і ненадійною саме її визначенням).

Наскільки я розумію, ваше рішення (1) змушує всю структуру бути constвсякою, коли є одне з її полів const. Таким чином, s1._b = bбуло б незаконно для ._bполя, що не s1містить const, на non-const, що містить a const a.


Добре. Cледве має систему звукового типу (більше схожа на купу кутових корпусів, прив'язаних один до одного протягом багатьох років). Крім того, інший спосіб поставити завдання на a struct- це memcpy(s_dest, s_src, sizeof(S_t)). І я впевнений, що це реально реалізовано. І в такому випадку навіть існуюча система типу не забороняє вам це робити.
Михайло Паньков

2
Дуже правильно. Я сподіваюсь, що я не мав на увазі, що система типу C є здоровою, лише те, що навмисне роблячи певну семантику нерозбірливою, навмисно перемагає її. Більше того, хоча система типів C не застосовується сильно, шляхи її порушення часто є явними (покажчики, непрямий доступ, касти) - хоча його ефекти часто неявні і важко відслідковувати. Таким чином, наявність явних «огорож» для їх порушення інформує краще, ніж мати протиріччя в самих визначеннях.
Тіаго Сільва

2

Причина полягає в тому, що поля лише для читання - лише для читання. Ніякого великого сюрпризу там немає.

Ви помилково припускаєте, що єдиний ефект полягає у розміщенні в ПЗУ, що справді неможливо, якщо є суміжні поля, які не мають const. Насправді оптимізатори можуть припускати, що constвирази не записуються, а на основі цього оптимізуються. Зрозуміло, що припущення не виконується, коли існують непровідні псевдоніми.

Ваше рішення (1) порушує існуючий юридичний та розумний код. Цього не станеться. Ваше рішення (2) в значній мірі видаляє значення constчленів. Незважаючи на те, що це не порушить існуючий код, він, мабуть, бракує обґрунтування.


Я на 90% впевнений, що оптимізатори можуть не вважати, що constполя не записуються, тому що ви завжди можете використовувати memsetабо memcpy, і це навіть було б відповідати Стандарту. (1) може бути реалізований як принаймні додаткове попередження, включене прапором. Обґрунтування (2) полягає в тому, що, точно, немає жодного способу, коли компонент не structможе вважатися незаписним, коли вся структура може бути записана.
Михайло Паньков

"Факультативна діагностика, визначена прапором", була б унікальною вимогою, яку вимагає Стандарт. Крім того, встановлення прапора все-таки порушить існуючий код, тому фактично ніхто не заважає прапору, і це було б тупиком. Що стосується (2), 6.3.2.1: х вказує прямо протилежне: вся структура не пишеться, коли є один компонент. Однак інші компоненти все ще можуть бути записані. Ср. C ++, який визначається також operator=з точки зору членів, і тому не визначає, operator=коли один член є const. C і C ++ все ще сумісні тут.
MSalters

@constantius - Той факт, що ви МОЖЕТЕ зробити щось, щоб навмисно обійти чіткість члена, НЕ є приводом для оптимізатора ігнорувати цю стиглість. Ви МОЖЕТЕ відкинути незграбність усередині функції, що дозволяє змінювати речі. Але оптимізатору в контексті виклику все ж дозволяється припускати, що ви цього не зробите. Констрисність корисна для програміста, але в деяких випадках також є оптимальним оптимізатором.
Майкл Коне

Тоді чому не можна записати структуру, що не записується, тобто memcpy? Що стосується інших причин - нормально, це спадщина, але чому це робилося в першу чергу?
Михайло Паньков

1
Мені все ще цікаво, чи правильний ваш коментар memcpy. AFACIT Цитата Джона Боде у вашому іншому запитанні є правильною: ваш код записується на об'єкт, що відповідає кваліфікації, і тому НЕ стандартна скарга, кінець обговорення
MSalters
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.