Почему глобальные и локальные статические утверждения C11 ведут себя по-разному? - PullRequest
0 голосов
/ 04 ноября 2019

Рассмотрим следующий код:

const int g_foo = 1;

// (1):
_Static_assert(g_foo > 0, "g_foo > 0");  // error: expression in static assertion is not constant.
_Static_assert(g_foo > 2, "g_foo > 2");  // error: expression in static assertion is not constant.

void Foo(void) {
  const int foo = 1;

  // (2):
  _Static_assert(foo > 0, "foo > 0");  // No issue.
  _Static_assert(foo > 2, "foo > 2");  // error: static assertion failed: "foo > 2"

  // (3):
  _Static_assert(g_foo > 0, "g_foo > 0");  // No issue.
  _Static_assert(g_foo > 2, "g_foo > 2");  // error: static assertion failed: "g_foo > 2"
}

Компиляция с использованием gcc -std=c11 -O3 test.c с версией 7.4.0 GCC выдает указанные сообщения об ошибках. Пример компилятора с использованием GCC 9.2 также можно найти здесь .

Начиная со статических утверждений, помеченных (2), мы используем локальную переменную с квалификацией const. На этом уровне оптимизации const int foo = 1; оптимизируется, и, следовательно, инициализатор не оценивается. Из того, что я понимаю, это классифицирует его как константное выражение в соответствии с 6.6.3 ISO / IEC 9899: 2011 * и позволяет оценивать его по _Static_assert**. Пока все хорошо.

Статические утверждения, помеченные (1), пытаются вычислить выражения, содержащие глобальную переменную g_foo, но тот факт, что глобальные ресурсы выделяются в памяти, означает, что их действительно нужно инициализировать. Эта оценка автоматически дисквалифицирует их как выражение с постоянным выражением, и GCC жалуется соответственно.

Затем мы переходим к (3). Внезапно статические утверждения, которые потерпели неудачу в глобальной области видимости, начинают работать. Что происходит?

* К сожалению, я использую проект комитета 2010-12-02 вместо окончательной опубликованной спецификации.

** Как интересно,использование ненулевого уровня оптимизации важно в этом вопросе. При -O0 переменная foo фактически создается с помощью литерала 1. Однако это больше не является константным выражением, и статические утверждения, использующие foo, начинают терпеть неудачу с «выражением в статическом утверждении не является константой».


Правки (2019-11-04):

  • Удалено *(int *)&g_foo = -1; из блока кода - это неопределенное поведение и отвлекает от основного вопроса.
  • Удалено лишнее замечание о приведении const квалифицированных переменных в C.
  • Добавлена ​​ссылка на Compiler Explorer, чтобы помочь с воспроизведением проблемы.

Ответы [ 2 ]

4 голосов
/ 04 ноября 2019

*(int *)&g_foo = -1; является явно неопределенным поведением в соответствии с C17 6.7.3 / 6:

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

(черновик C11 n1548 говорит то же самое)

Поэтому вы не можете разумно рассуждать о поведении этого кода.

Лично я получаю все 6 подтверждений для срабатывания. Пробовал как со старым gcc 4.8 так и 9.2 / trunk, с включенным оптимизатором или без него. У меня отлично работает, но, видимо, вы получаете разные результаты на вашем локальном компиляторе. Такова природа неопределенного поведения.

3 голосов
/ 04 ноября 2019

Это не работает, потому что

Стандарт C требует, чтобы первый аргумент _Static_assert был константным выражением целочисленного типа.

static_assert-declaration:
         _Static_assert ( constant-expression , string-literal ) ;

https://port70.net/~nsz/c/c11/n1570.html#6.7.10

Стандарт гласит это о целочисленных константных выражениях:

Целочисленное константное выражение должно иметь целочисленный типи должен иметь только операнды, которые являются целочисленными константами, константами перечисления, символьными константами, выражениями sizeof, результатом которых являются целочисленные константы, выражения _Alignof и плавающие константы, которые являются непосредственными операндами приведений.

https://port70.net/~nsz/c/c11/n1570.html#6.6p6

Перечисляемые константы, символьные константы, выражения sizeof, выражения _Alignof и плавающие константы здесь явно не актуальны. Это оставляет нам только целочисленные константы. И стандарт говорит это о целочисленных константах:

integer-constant:
      decimal-constant integer-suffixopt
      octal-constant integer-suffixopt
      hexadecimal-constant integer-suffixopt
decimal-constant:
      nonzero-digit
      decimal-constant digit
octal-constant:
      0
      octal-constant octal-digit
hexadecimal-constant:
      hexadecimal-prefix hexadecimal-digit
      hexadecimal-constant hexadecimal-digit
hexadecimal-prefix: one of
      0x   0X
nonzero-digit: one of
      1   2   3   4   5   6   7   8   9
octal-digit: one of
      0   1   2   3   4   5   6   7
hexadecimal-digit: one of
      0   1   2   3   4   5   6   7   8   9
      a   b   c   d   e   f
      A   B   C   D   E   F
integer-suffix:
      unsigned-suffix long-suffixopt
      unsigned-suffix long-long-suffix
      long-suffix unsigned-suffixopt
      long-long-suffix unsigned-suffixopt
unsigned-suffix: one of
      u   U
long-suffix: one of
      l   L
long-long-suffix: one of
      ll   LL

https://port70.net/~nsz/c/c11/n1570.html#6.4.4.1p1

Обратите внимание, что определение целочисленной константы НЕ включает переменные, объявленные с квалификатором const,Объявление переменной как const только делает невозможным (или я должен сказать незаконным) ее изменение. Это не делает его постоянным выражением. "Почему нет?"Вы можете по праву спросить. Ну, я не знаю, но я подозреваю, что была некоторая неясная историческая причина, когда они стандартизировали C, который не актуален сегодня, но все еще живет. Помните, что при разработке API: s!

Следовательно, g_foo не считается константным выражением , и стандарт не требует никаких ваших утверждений для работы.

Но обратите внимание, что мы можем прочитать здесь:

Реализация может принимать другие формы константных выражений.

https://port70.net/~nsz/c/c11/n1570.html#6.6p10

Возможно, это работает для 2 и 3 из-за расширений компилятора. Я думаю, вам придется прочитать документацию компилятора для подробностей или надеяться, что кто-то другой придет с ответом.

Что вы можете сделать?

Есть обходной путь, который вы можете использовать. Обратите внимание, что константы перечисления учитываются как целочисленные константы и, следовательно, также как целочисленные константные выражения. Измените

const int g_foo = 1;

на

enum { g_foo = 1; }
...