Массив как составной литерал
В C99 мы можем использовать составные литералы как безымянный массив.
Но это литеральные константы, как, например, 100
, 'c'
, 123.4f
, так далее.
Я заметил, что я могу сделать:
((int []) {1,2,3})[0] = 100;
и у меня нет ошибки компиляции, и я предполагаю, что первый элемент этого безымянного массива изменен на 100.
Таким образом, кажется, что массив, а составной литерал lvalue, а не постоянное значение.
4 ответа
Это lvalue, мы можем увидеть это, если мы посмотрим на черновик стандартного раздела C99 6.5.2.5
Сложные литералы это говорит (выделение мое):
Если имя типа задает массив неизвестного размера, размер определяется списком инициализатора, как указано в 6.7.8, а тип составного литерала - тип завершенного массива. В противном случае (когда имя типа указывает тип объекта), тип составного литерала соответствует типу, указанному в имени типа. В любом случае результатом является lvalue.
Если вам нужна const- версия, далее в том же разделе приведен следующий пример:
ПРИМЕР 4 Составной литерал только для чтения может быть указан с помощью таких конструкций:
(const float []){1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6}
Мы можем найти объяснение терминологии в статье д-ра Добба " Новые C: составные литералы", где говорится:
Составные литералы не являются истинными константами в том смысле, что значение литерала может измениться, как показано ниже. Это подводит нас к терминологии. Стандарты C99 и C90 [2, 3] используют слово "константа" для токенов, которые представляют собой действительно неизменяемые значения, которые невозможно изменить в языке. Таким образом, 10 и 3.14 являются целочисленной десятичной константой и плавающей константой типа double соответственно. Слово "литерал" используется для представления значения, которое может быть не таким постоянным. Например, ранние реализации C допускали изменение значений строк в кавычках. C90 и C99 запретили эту практику, заявив, что любая программа, которая модифицировала строковый литерал, имела неопределенное поведение, что является стандартом для того, чтобы сказать, что это может сработать, или программа может выйти из строя таинственным образом. [...]
Составные литералы являются lvalues, и его элементы могут быть модифицируемыми. Вы можете присвоить ему значение. Допускается даже указатель на составные литералы.
Обращаясь к проекту стандарта C11 N1570:
Раздел 6.5.2.5p4:
В любом случае результатом является lvalue.
"Lvalue" - это, грубо говоря, выражение, обозначающее объект, но важно отметить, что не все lvalue являются изменяемыми. Простой пример:
const int x = 42;
Имя x
это lvalue, но это не модифицируемое lvalue. (Выражения типа массива не могут быть модифицируемыми lvalues, потому что вы не можете назначить объект массива, но элементы массива могут быть модифицируемыми.)
Пункт 5 того же раздела:
Значение составного литерала - это значение безымянного объекта, инициализированного списком инициализатора. Если составной литерал находится вне тела функции, объект имеет статическую продолжительность хранения; в противном случае он имеет автоматическую продолжительность хранения, связанную с окружающим блоком.
Раздел, описывающий составные литералы, специально не говорит, является ли безымянный объект изменяемым или нет. В отсутствие такого утверждения объект считается изменяемым, если тип не const
-qualified.
Пример в вопросе:
((int []) {1,2,3})[0] = 100;
не особенно полезен, так как после присвоения невозможно ссылаться на неназванный объект. Но подобная конструкция может быть весьма полезной. Придуманный пример:
#include <stdio.h>
int main(void) {
int *ptr = (int[]){1, 2, 3};
ptr[0] = 100;
printf("%d %d %d\n", ptr[0], ptr[1], ptr[2]);
}
Как уже упоминалось выше, массив имеет автоматическую продолжительность хранения, что означает, что если он создан внутри функции, он перестанет существовать, когда функция вернется. Составные литералы не являются заменой malloc
,
Насколько я помню, вы правы, составные литералы являются lvalues *, вы также можете взять указатель такого литерала (который указывает на его первый элемент):
int *p = (int []){1, 2, 3};
*p = 5; /* modified first element */
Также можно применять const
квалификатор для такого составного литерала, поэтому элементы доступны только для чтения:
const int *p = (const int []){1, 2, 3};
*p = 5; /* wrong, violation of `const` qualifier */
* Обратите внимание, это не означает, что это автоматически изменяемое значение lvalue (поэтому его можно использовать в качестве левого операнда для оператора присваивания), поскольку оно имеет тип массива и ссылается на черновик C99 6.3.2.1
L-значения, массивы и функциональные обозначения:
Изменяемое lvalue - это lvalue, у которого нет типа массива, [...]