Утверждения хорошие. Утверждения времени компиляции еще лучше. Примечание:
Если в вашей среде еще нет статического утверждения, вот предложение.
Макрос ASSERT()
, приведенный ниже, может быть размещен в любом месте кода, кроме:
- В заголовочном файле, включенном дважды, без оболочки
#ifndef...#endif
.
- В середине определения структуры (или определения enum).
- В строгом C89 или C90, после заявления. (Но вы можете завернуть его в фигурные скобки!)
Если вы хотите вставить что-то в середину определения структуры, вам нужно использовать длинную, некрасивую трехстрочную конструкцию #if...#error...#endif
. И если вы сделаете это, препроцессор будет иметь гораздо более ограниченное представление о том, что такое "константное выражение".
Это уточнение идей из Интернета, в основном из http://www.pixelbeat.org/programming/gcc/static_assert.html. Это определение короче BOOST_STATIC_ASSERT()
. И, я полагаю, лучше, чем предложение Линуса об улучшении BUILD_BUG_ON()
. А оболочка do{...}while(0)
, которую вы обычно видите, здесь совершенно неприменима, поскольку ограничивает допустимые местоположения.
Это также проще, чем Google COMPILE_ASSERT / CompileAssert. Трюк «sizeof bitfield» также кажется хорошим, из Linux BUILD_BUG_ON_ZERO()
, но не его бесполезный брат BUILD_BUG_ON()
.
Существует множество предложений по использованию массивов с отрицательным индексом. Но с GCC большинство из них не обнаруживают непостоянную arg (что достаточно легко сделать по ошибке), за исключением extern int foo [выражение], которое также дает «неиспользованный» переменная 'предупреждение. Но typedef int array[expression]
кажется также хорошим: см. Ниже.
Определение:
#define CONCAT_TOKENS(a, b) a ## b
#define EXPAND_THEN_CONCAT(a,b) CONCAT_TOKENS(a, b)
#define ASSERT(e) enum{EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__) = 1/!!(e)}
Не менее хорош, как мне кажется, следующий вариант, но он длиннее на пять символов:
#define ASSERT(e) typedef int EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__)[1-2*!(e)]
Существует также конструкция do{switch(0){case 0:case(e):;}}while(0)
, которую я не исследовал.
Иногда нужен вариант для обработки случая, когда два разных заголовочных файла случайно имеют два ASSERT () в одной строке, или аналогично для исходного файла и заголовочного файла. Вы можете справиться с этим с помощью __COUNTER__
, но это не поддерживается некоторыми компиляторами (и это уродливее). И мы не можем использовать __FILE__
, потому что он обычно не расширяется до действительного токена C (например, у него есть точка c или точка h). Версия Mozilla http://mxr.mozilla.org/mozilla-central/source/mfbt/Assertions.h гласит, что такие конфликты "должны быть редкими", но они будут сильно раздражать ваших товарищей по команде, когда это произойдет. Это также можно использовать для обработки нескольких ASSERTS
в многострочном макросе, где __LINE__
не меняется.
#define ASSERTM(e,m) enum{EXPAND_THEN_CONCAT(m##_ASSERT_line_,__LINE__)=1/!!(e)}
Следующий вариант ASSERT_zero(),
аналогичен BUILD_BUG_ON_ZERO(),
с использованием трюка «sizeof bitfield». Это дает либо:
- ошибка компиляции, когда
e
имеет значение false или
- значение ноль.
Так что его можно использовать в местах, где оператор не может, например, в середине выражения.
#ifndef __cplusplus
#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // cf. BUILD_BUG_ON_ZERO(), !C++
#else
#define ASSERT_zero(e) (!sizeof(char[(e) ? 1 : -1])) // careful: g++ has VLAs
#endif