Подробности того, что составляет постоянное выражение в C?

C определяет как минимум 3 уровня «постоянного выражения»:

  • постоянное выражение (неквалифицированное)
  • арифметическое постоянное выражение
  • целочисленное постоянное выражение

6.6 пункт 3 гласит:

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

Значит ли это, что 1,2 не является постоянным выражением?

Пункт 8 гласит:

Выражение арифметической константы должно иметь арифметический тип и иметь только операнды, которые являются целочисленными константами, плавающими константами, константами перечисления, символьными константами и выражениями sizeof. Операторы приведения в выражении арифметической константы должны преобразовывать только арифметические типы в арифметические типы, за исключением части операнда в оператор sizeof, результатом которого является целочисленная константа.

Какие операнды в (union { uint32_t i; float f; }){ 1 }.f? Если 1 является операндом, то это предположительно арифметическое постоянное выражение, но если { 1 } является операндом, то это явно не так.

Изменить: Еще одно интересное наблюдение: пункт 3 7.17 требует, чтобы результат offsetof был целочисленным постоянным выражением типа size_t, но стандартные реализации offsetof, насколько я могу судить, не обязательно должны быть целочисленные константные выражения по стандарту. Это, конечно, нормально, поскольку реализации разрешено (в соответствии с параграфом 10 6.6) принимать другие формы константных выражений или реализовывать макрос offsetof как __builtin_offsetof, а не через вычитание указателя. Однако суть этого наблюдения заключается в том, что если вы хотите использовать offsetof в контексте, где требуется целочисленное постоянное выражение, вам действительно нужно использовать макрос, предоставленный реализацией, а не использовать собственный.


person R.. GitHub STOP HELPING ICE    schedule 04.02.2011    source источник
comment
В 1,2 я думаю, что 1 - это постоянное выражение, а 2 - это непрерывное выражение.   -  person Pawel Zubrycki    schedule 04.02.2011
comment
@Chris: Объединения действительны как составные литералы, но я сомневаюсь, может ли результат быть арифметическим константным выражением. @Pawel: Мой вопрос касался выражения 1,2, в котором используется оператор запятой, который по какой-то причине, которую я не могу объяснить, похоже, был исключен из операторов, разрешенных в постоянных выражениях.   -  person R.. GitHub STOP HELPING ICE    schedule 04.02.2011
comment
@R .. - Я узнал об этом около минуты назад, проверив на себе, и удалил свой комментарий.   -  person Chris Lutz    schedule 04.02.2011
comment
@R ..: Потому что есть два постоянных выражения, а не одно. В отдельные выражения исключена запятая.   -  person Pawel Zubrycki    schedule 04.02.2011
comment
@Pawel - Это не имеет смысла. Запятая - это оператор, поэтому при ее использовании создается выражение, как и любой другой оператор. Нет причин, по которым я могу понять, почему следует запретить оператору запятой быть в постоянном выражении, если только сообщество стандартов не предполагало, что никто никогда не будет использовать его с первым аргументом, который не имеет побочных эффектов.   -  person Chris Lutz    schedule 04.02.2011
comment
@ Крис: В этом есть смысл. Кто бы мог использовать запятую в одном выражении? Это оператор, разделяющий выражения. Должен быть тот, кто это делает, потому что иначе как бы вы их разделили?   -  person Pawel Zubrycki    schedule 04.02.2011
comment
@Pawel - 1,2 - это одно выражение. Он состоит из двух целочисленных литералов, 1 и 2, в качестве аргументов запятой operator (6.5.17) для создания одного выражения. Вопрос R .. в том, что если и 1, и 2 являются константными выражениями, почему 1,2 непостоянное выражение?   -  person Chris Lutz    schedule 04.02.2011
comment
Быстрый тест с gcc 4.3.4 показывает, что он отклоняет как enum {a=(1,2)};, так и enum {b=(struct {int i;}){1}.i};. Конечно, это не имеет отношения к стандарту.   -  person Joseph Quinsey    schedule 04.02.2011
comment
Одно, возможно, полезное следствие запрета оператора запятой в постоянных выражениях заключается в том, что вы можете использовать (void *)(0,0) для построения преобразования 0 (как int) в указатель, что отличается от преобразования целочисленного постоянного выражения 0 в указатель. (последний является нулевым указателем). К сожалению, стандарт допускает дополнительные константные выражения, определяемые реализацией, поэтому я полагаю, что это ненадежно ....   -  person R.. GitHub STOP HELPING ICE    schedule 26.03.2011
comment
@R ..: Считается ли (void*)(1-1) синонимом (void*)0?   -  person supercat    schedule 02.09.2014
comment
@supercat: Да. Любое целочисленное константное выражение со значением ноль, независимо от типа или того, как оно выражено, является константой нулевого указателя, и если вы приведете его к void *, это будет константа нулевого указателя с известным фиксированным типом (void *) и, таким образом, синонимом для всех целей, кроме преобразования в строку через препроцессор. :-)   -  person R.. GitHub STOP HELPING ICE    schedule 02.09.2014


Ответы (2)


Судя по вашему чтению, 1,2 не является постоянным выражением. Я не знаю, почему это не так, просто я согласен с вами, что это не так (несмотря на то, что, вероятно, так и должно быть).

6.5.2 определяет составные литералы как постфиксный оператор. Так что в

(union { uint32_t i; float f; }){ 1 }.f

Операнды (union { uint32_t i; float f; }){ 1 } и f для оператора .. Это не арифметическое постоянное выражение, поскольку первый аргумент имеет тип union, но это постоянное выражение.

ОБНОВЛЕНИЕ: я основывал это на другой интерпретации стандарта.

Мое предыдущее рассуждение заключалось в том, что (union { uint32_t i; float f; }){ 1 }.f удовлетворяет критериям постоянного выражения и, следовательно, является постоянным выражением. Я по-прежнему считаю, что оно соответствует критериям константного выражения (6.6 абзац 3), но не является ни одним из стандартных типов константных выражений (целые, арифметические или адресные) и, следовательно, может быть константным выражением только в абзаце 6.6. 10, что позволяет использовать константные выражения, определяемые реализацией.

Я тоже хотел перейти к твоему редактированию. Я собирался возразить, что «взломанная» реализация offsetof была константным выражением, но я думаю, что это то же самое, что и выше: оно соответствует критериям для константного выражения (и, возможно, константы адреса), но не является целочисленным константным выражением, и поэтому недействителен за пределами пункта 10 6.6.

person Chris Lutz    schedule 04.02.2011
comment
Интересно, как левый операнд. operrator может быть постоянным выражением. Как вы сказали, это профсоюзный тип. Инициализация выполняется без использования арифметического оператора, но выглядит очень подозрительно. - person Windows programmer; 04.02.2011
comment
Программист @Windows - см. 6.5.2.5 для описания составных литералов (добавленных в C99) и 6.6 для описания константных выражений. Насколько я могу судить, это соответствует требованиям. - person Chris Lutz; 04.02.2011
comment
Теперь мне интересно, является ли abcdef [2] постоянным выражением со значением 'c'. Я почти уверен, что раньше это не рассматривалось как постоянное выражение, но сейчас я не могу найти причину. - person Windows programmer; 04.02.2011
comment
Программист @Windows: в параграфе 7 перечислены типы константных выражений: арифметика, нулевой указатель, адресная константа и адрес плюс смещение. Из них строки могут быть действительными только в последних двух, и правила для них явно запрещают любой доступ к значению объекта. Итак, "abcdef"[2] не является постоянным выражением. - person R.. GitHub STOP HELPING ICE; 04.02.2011
comment
Принял этот ответ, потому что он, кажется, отвечает на мои конкретные вопросы. Но я все равно буду рад услышать больше по этой теме. - person R.. GitHub STOP HELPING ICE; 04.02.2011
comment
@R .. - Я тоже, особенно о том, почему исключены запятые. Запрещение int a[1,2]; кажется произвольным, когда вы разрешаете так много других вещей. - person Chris Lutz; 04.02.2011
comment
Хм, ОК abcdef [2] все еще не постоянное выражение, но (struct {char a; char b; char c; char d; char e; char f;}) {'a', 'b', 'c' , 'd', 'e', ​​'f'} .c равно единице. Последнее выражение может занимать больше места в памяти и иметь другие несущественные отличия. Я лучше пойду вычищу туалетный лоток моей кошки - он пахнет лучше, чем сейчас. - person Windows programmer; 04.02.2011
comment
Программист @Windows - постоянное выражение вычисляется во время компиляции и не занимает места в памяти. Я не понимаю, что вы добавляете к этому обсуждению. - person Chris Lutz; 04.02.2011
comment
Сноска 82 на странице 76 и пример 6 на странице 77 не являются нормативными, но они поясняют, что нормативный текст позволяет составному литералу занимать память. Вот почему кажется странным, что стандарт разрешает доступ к некоторым хранилищам в постоянных выражениях, но не разрешает доступ к другим хранилищам в постоянных выражениях. Я согласен с вами, что формулировка стандарта имеет такой эффект, мне просто интересно, предполагал ли комитет такой эффект. - person Windows programmer; 04.02.2011
comment
Программист @Windows - они есть, но не указывают, что указанный объект должен иметь хранилище. Я бы сказал, что никакое хранилище не считается формой автоматического хранилища. - person Chris Lutz; 04.02.2011
comment
Программист @Windows: быстрый тест с gcc 4.3.4 показывает, что ни один из ваших двух примеров не считается им константным выражением. Так что gcc, по крайней мере, согласован. Конечно, это не имеет отношения к стандарту. - person Joseph Quinsey; 04.02.2011
comment
Программист @Windows: после прочтения я убедился, что никакое выражение, содержащее составной литерал, никогда не может быть константным выражением. gcc, похоже, согласен. - person R.. GitHub STOP HELPING ICE; 05.02.2011
comment
@Chris: параграф 7 6.6 определяет только 4 типа константных выражений: арифметические (которые явно могут иметь только арифметические типы в качестве операндов), константы нулевого указателя (даже более ограниченные) и адресные константы (со смещением или без него). Я не вижу места для константных выражений агрегатного типа. - person R.. GitHub STOP HELPING ICE; 05.02.2011
comment
@R .. - Верно. Я думаю, что он подпадает под категорию выражаемых констант (т. Е. Соответствует общим критериям для константного выражения), но не является ни одним из стандартных константных выражений (поэтому он должен быть определен реализацией в соответствии с п. 6.6 §10). Внесение поправок в мой ответ к этому (и другим вещам. Надо как-то продолжать). - person Chris Lutz; 05.02.2011

Если 1,2 было бы постоянным выражением, это позволило бы компилировать такой код:

{ // code        // How the compiler interprets:
  int a[10, 10]; // int a[10];

  a[5, 8] = 42;  // a[8] = 42;
}

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

ОБНОВЛЕНИЕ: как R. указывает в комментарии, код больше не является ошибкой компилятора с момента введения VLA.

person Sjoerd    schedule 04.02.2011
comment
@Chris Чаще, чем необходимость в 1,2 быть константным выражением. - person Sjoerd; 04.02.2011
comment
Я думаю, что причина, по которой 1,2 не является постоянным выражением, заключается в том, что int b [2] = {1,2}; предназначен для инициализации обоих элементов, а не только для инициализации b [0] значением 2. Между тем {int a [10]; а [5, 8] = 42; } - допустимый код, возможно, слишком простой для ioccc, но, возможно, предназначенный для thedailywtf. - person Windows programmer; 04.02.2011
comment
Также подумайте, есть ли в объявлении перечисления выражение, определяющее значение константы перечисления, и представьте, может ли это выражение быть 1,2. - person Windows programmer; 04.02.2011
comment
Очевидно, что запятая в инициализаторе не является оператором запятой; это не имеет ничего общего с тем, допустим ли оператор запятой в постоянном выражении. Как и в списке аргументов функции, вам нужны круглые скобки для использования оператора запятой в таких контекстах. Обратите внимание, что оператор запятой вполне допустим в инициализаторах для автоматических переменных, например int x = (1,2); - person R.. GitHub STOP HELPING ICE; 04.02.2011
comment
@Sjoerd: Я бы сказал, что необходимость использования оператора запятой в постоянном выражении может быть очень реальной с макросами. Предположим, у вас есть макрос, реализация которого под определенным #ifdef не использует ни одного из своих аргументов. Чтобы гарантировать, что он по-прежнему оценивает каждый аргумент ровно один раз, вы можете сделать что-то вроде #define FOO(a,b) ((a),BAR((b))). Если бы не произвольное правило, что запятая недопустима в постоянных выражениях, FOO можно было бы использовать в статических инициализаторах. - person R.. GitHub STOP HELPING ICE; 04.02.2011
comment
Обратите внимание, что оператор запятой вполне допустим в инициализаторах для автоматических переменных, например интервал х = (1,2); - не тогда, когда инициализатор состоит из списка выражений присваивания, которые не являются полностью общими выражениями. - person Windows programmer; 04.02.2011
comment
Чтобы гарантировать, что он по-прежнему оценивает каждый аргумент ровно один раз - это тривиально, независимо от того, задействованы ли макросы. (0 * (а) + (б)). Однако он оценивается во время компиляции, а не во время выполнения, поэтому мне интересно, что вы получите. - person Windows programmer; 04.02.2011
comment
Программист @Windows: я предполагаю, что один и тот же макрос может быть полезен как для константных выражений, так и для использования во время выполнения. И ваш 0*(a) трюк сломается, как только я скажу вам, что a имеет неарифметический тип ... :-) - person R.. GitHub STOP HELPING ICE; 04.02.2011
comment
@Sjoerd: на самом деле ваш ответ - отличный вопрос с подвохом: в чем разница между int a[5]; и int a[1,5];? Первый - это обычный массив, а второй - VLA. :-) - person R.. GitHub STOP HELPING ICE; 05.02.2011
comment
@Р. Отличный вопрос с подвохом! И да, хороший момент, что int a [1,5] больше не является ошибкой компиляции в C с тех пор, как был введен VLA. - person Sjoerd; 05.02.2011
comment
Я действительно не понимаю правил, когда sizeof оценивает свой операнд, но мне интересно, можно ли это использовать для создания ужасно запутанного кода ... - person R.. GitHub STOP HELPING ICE; 05.02.2011