статическое утверждение для C90 на gcc - PullRequest
0 голосов
/ 25 января 2019

static_assert() - это отличная возможность, доступная с C11.

Для компиляторов до C11 эта возможность должна быть эмулирована. Это не так уж сложно, есть много примеров, доступных через Интернет.

Например:

#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert_##MSG[(CONDITION)?1:-1]

Это позволяет передавать сообщение об ошибке в условии, что удобно для объяснения того, что происходит неправильно, если оно когда-либо срабатывает.

Однако, этот MSG сильно отличается от того, что в C11 static_assert():

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

Это настолько отличается от C11 static_assert(), что кажется невозможным создать макрос, который бы прозрачно переключался между версиями C11 и C90 в зависимости от компилятора.

В попытке принять сообщение об ошибке, которое «выглядит как C11», то есть строка с двойной кавычкой, я протестировал новый макрос:

#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert[((void)(MSG), ((CONDITION)?1:-1))]

Используя оператор запятой ,, этот макрос должен принимать MSG в виде строки и просто игнорировать ее. Но это будет отображаться в случае ошибки, которая является намерением.

Отлично работает на clang, , но не на gcc: error: variably modified at file scope.

Я пытаюсь понять, почему, и если есть работа вокруг

1 Ответ

0 голосов
/ 25 января 2019

Если вы замените трюк typedef-array-trick на enum-trick, то вы получите что-то, что работает как с clang, так и с gcc:

#define CONDITION 1

#define TOKENPASTE(a, b) a ## b // "##" is the "Token Pasting Operator"
#define TOKENPASTE2(a,b) TOKENPASTE(a, b) // expand then paste
#define static_assert(x, msg) enum { TOKENPASTE2(ASSERT_line_,__LINE__) \
    = 1 / (msg && (x)) }

static_assert( CONDITION, "This should pass");
static_assert(!CONDITION, "This should fail");

Это дает мне, например, gcc в строке 9 файла foo.c:

foo.c:9: warning: division by zero [-Wdiv-by-zero]
 static_assert(!CONDITION, "This should fail");
 ^
foo.c:9: error: enumerator value for 'ASSERT_line_9' is not an integer constant
 static_assert(!CONDITION, "This should fail");
 ^~~~~~~~~~~~~

(Здесь используется переключатель gcc -ftrack-macro-expansion=0, поскольку дополнительные сообщения об ошибках не очень полезны и просто добавляют шум.)

Обратите внимание, что искажение имени по-прежнему необходимо, но вы его не указали. Здесь текст ASSERT_line_ объединяется с переменной __LINE__. Это обеспечивает уникальное имя при условии:

  • Вы не используете его дважды в одной строке.
  • Вы не используете его в заголовочных файлах (или доверяете удаче).
  • Ваш код не использует идентификаторы, такие как ASSERT_line_9 в других местах.

Для заголовочных файлов вам нужно будет где-то добавить одно слово только с символами-идентификаторами. Например:

#define static_assert3(x, msg, file) enum { TOKENPASTE2(file,__LINE__) = \
    1 / (msg && (x)) }
#define static_assert(x, msg) static_assert3(x, msg, my_header_h_)

Если в строке 17 произойдет сбой, gcc выдаст ошибку, такую ​​как:

 error: enumerator value for 'my_header_h_17' is not an integer constant

Альтернативой для искажения в заголовочных файлах является замена __LINE__ на __COUNTER__. Я не использовал его, потому что он нестандартный и потому что Clang не спешил его принимать. Но сейчас он работает в gcc, msvc и clang около пяти лет.


Вы можете попробовать ту же модификацию с вашей идеей typedef-array и заменить оператор запятой на &&. Тогда ваша ошибка gcc изменится на предупреждение. Например, изменив свой пример с Godbolt:

typedef char static_assert_2["hello world!" && (CONDITION) ? 1 : -1];

дает нежелательные warning: variably modified 'static_assert_2' at file scope за GCC.

...