Struct vs строковые литералы? Только чтение или чтение-запись?

Допускает ли стандарт C99 запись в составные литералы (структуры)? Кажется, он не обеспечивает запись в буквальные строки. Я спрашиваю об этом, потому что в Программирование на C: современный подход, 2-е издание на Стр. 406.

В. Разрешение указателя на составной литерал делает возможным его изменение. Так ли это?

А. Да. Составные литералы - это значения l, которые можно изменять.

Но я не совсем понимаю, как это работает и как это работает со строковыми литералами, которые вы определенно не можете изменить.

char *foo = "foo bar";
struct bar { char *a; int g; };
struct bar *baz = &(struct bar){.a = "foo bar", .g = 5};

int main () {
  // Segfaults
  // (baz->a)[0] = 'X';
  // printf( "%s", baz->a );

  // Segfaults
  // foo[0] = 'a';
  // printf("%s", foo);

  baz->g = 9;
  printf("%d", baz->g);

  return 0;
}

Вы можете видеть в моем списке вещей, которые segfault, запись в baz->a вызывает segfault. Но запись в baz->g - нет. Почему это один из них вызовет segfault, а не другой? Чем структурные литералы отличаются от строковых литералов? Почему структурные литералы также не помещаются в раздел памяти, доступный только для чтения, и определено ли поведение для обоих из них (вопрос стандарта)?


person Evan Carroll    schedule 23.08.2018    source источник
comment
Хороший вопрос, кажется, на самом деле никто не знает ответа. Тот же вопрос здесь: Массив как составной литерал. Тогда, как и сейчас, никто не мог доказать, что в составные литералы можно писать. То есть докажите, что они являются изменяемыми lvalue, а не просто lvalues ​​.   -  person Lundin    schedule 24.08.2018
comment
На чем основывается этот вопрос в первую очередь на мнении? Проголосовали за повторное открытие.   -  person haccks    schedule 24.08.2018


Ответы (3)


Перво-наперво: ваш структурный литерал имеет член-указатель, инициализированный строковым литералом. Члены самой структуры доступны для записи, включая член-указатель. Запрещается только содержимое строкового литерала.

Строковые литералы были частью языка с самого начала, тогда как структурные литералы (официально известные как составные литералы) появились относительно недавно, начиная с C99. К тому времени существовало множество реализаций, которые помещали строковые литералы в постоянную память, особенно во встроенных системах с крошечным объемом оперативной памяти. К тому времени у разработчиков стандарта был выбор: требовать перемещения строковых литералов в доступное для записи место, позволяя структурным литералам быть доступными только для чтения, или оставлять все как есть. Ни одно из трех решений не было идеальным, поэтому похоже, что они пошли по пути наименьшего сопротивления и оставили все как есть.

Допускает ли стандарт C99 запись в составные литералы (структуры)?

Стандарт C99 не запрещает явно запись в объекты данных, инициализированные составными литералами. Это отличается от строковых литералов, модификация которых по стандарту считается неопределенным поведением.

person Sergey Kalinichenko    schedule 23.08.2018
comment
Круто. Я отмечу это как принятое, если больше никому нечего добавить, потому что даже это отвечает на мой вопрос, и я считаю, что это ценный вклад в сеть: литералы Struct появились позже и отличаются друг от друга. Кажется, что некоторые тексты используют литералы как функцию синтаксиса, а другие - как функцию реализации (сопоставленную с массивом символов). Спасибо. - person Evan Carroll; 24.08.2018
comment
C не вызывает &(struct bar){.a = "foo bar", .g = 5} структурный литерал. Вместо этого он называется составным литералом. - person chux - Reinstate Monica; 24.08.2018
comment
Как это отвечает на вопрос: разрешает ли стандарт C99 запись в составные литералы (структуры)? - person Lundin; 24.08.2018

Стандарт по существу определяет одни и те же характеристики для строковых литералов и для составных литералов с типом с указанием const, используемым вне тела функции.

Продолжительность жизни

  • # P2 #
    # P3 #
  • # P4 #
    # P5 #

Возможно, поделился

  • Могут использоваться как строковые литералы, так и составные литералы с указанием const. Вы должны быть готовы к такой возможности, но не должны полагаться на нее.

6.4.5p7 Не указано, являются ли [массивы, созданные для строковых литералов] различными, при условии, что их элементы имеют соответствующие значения.

6.5.2.5p7 Строковые литералы и составные литералы с типами, квалифицируемыми как const, не должны обозначать отдельные объекты.

Изменчивость

  • Изменение либо строкового литерала, либо составного литерала с указанием const является неопределенным поведением. В самом деле, попытка изменить любой const-квалифицированный объект является неопределенным поведением, хотя формулировка стандарта, вероятно, может быть несущественной.

6.4.5p7 Если программа пытается изменить [массив, содержащий строковый литерал], поведение не определено.

6.7.3p6 Если предпринята попытка изменить объект, определенный с const-квалифицированным типом, посредством использования lvalue с неконстантным типом, поведение не определено.

  • Составной литерал, не квалифицированный как const, можно свободно изменять. У меня нет цитаты по этому поводу, но тот факт, что модификация явно не запрещена, кажется мне окончательным. Нет необходимости явно указывать, что изменяемые объекты могут быть видоизменены.

Тот факт, что время жизни составных литералов внутри тела функций является автоматическим, может привести к небольшим ошибкам:

/* This is fine */
const char* foo(void) {
  return "abcde";
}

/* This is not OK */
const int* oops(void) {
  return (const int[]){1, 2, 3, 4, 5};
;
person rici    schedule 24.08.2018
comment
Единственное, что имеет значение, это то, является ли составной литерал изменяемым lvalue или нет. Я не могу найти текста, заявляющего об этом в стандарте - составной литерал - это lvalue, но неясно, можно ли его изменить. Таким образом, ни один из этих ответов не отвечает, если составные литералы читаются / записываются. - person Lundin; 24.08.2018
comment
@lundin: Я думаю, что стандарт понятен. Если составной литерал имеет тип с квалификацией const, он не является изменяемым lvalue (6.3.2.1), поэтому его нельзя использовать с оператором мутации, как и ни один из его членов. Попытка обойти это, отбросив константу, будет UB (6.7.3p6). Если это не константный тип, то это неконстантный объект со статическим или автоматическим временем жизни; тот факт, что объект был создан составным литералом, не имеет значения, и объект является изменяемым. Я добавил примечание о сроках службы, потому что я часто вижу эту ошибку, а GCC, похоже, не предупреждает. - person rici; 24.08.2018

Допускает ли стандарт C99 запись в составные литералы (структуры)?

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

C99-6.5.2.5:

Если имя типа определяет массив неизвестного размера, размер определяется списком инициализаторов, как указано в 6.7.8, а тип составного литерала соответствует типу завершенного массива. В противном случае (когда имя типа определяет тип объекта) тип составного литерала - это тот, который указан в имени типа. В любом случае результатом будет lvalue.

Это означает, что составные литералы - это lvalue как массивы, а элементы составного литерала могут быть изменены, так же как вы можете изменить тип агрегата. Например

// 1
((int []) {1,2,3})[0] = 100;  // OK

// 2
(char[]){"Hello World"}[0] = 'Y';  // OK. This is not a string literal!

// 3
char* str = (char[]){"Hello World"};
*str = 'Y';  // OK. Writing to a compound literal via pointer. 

// 4
(const float []){1e0, 1e1, 1e2}[0] = 1e7 // ERROR. Read only compound literal 

В вашем коде вы пытаетесь изменить составной литеральный элемент, который указывает на немодифицируемый строковый литерал. Если этот элемент инициализирован составным литералом, его можно изменить.

struct bar *baz = &(struct bar){.a = (char[]){"foo bar"}, .g = 5};

Этот фрагмент теперь будет работать

Segfaults
(baz->a)[0] = 'X';
printf( "%s", baz->a );

Дальнейший стандарт также дает пример в том же разделе, упомянутом выше, и различает строковый литерал, составной литерал и составной литерал только для чтения:

13 ПРИМЕР 5 Следующие три выражения имеют разные значения:

"/tmp/fileXXXXXX"
(char []){"/tmp/fileXXXXXX"}
(const char []){"/tmp/fileXXXXXX"}

Первый всегда имеет статическую продолжительность хранения и имеет массив типа char, но не требует изменения; последние два имеют автоматическую продолжительность хранения, когда они встречаются в теле функции, а первый из этих двух можно изменить.

person haccks    schedule 24.08.2018