Способы ASSERT выражений во время сборки в C
Я убираю некоторый старый код, который повсеместно использует "магические числа" для установки аппаратных регистров, и я хотел бы использовать константы вместо этих чисел, чтобы сделать код несколько более выразительным (на самом деле они будут отображаться на имена / значения, используемые для документирования регистров).
Тем не менее, я обеспокоен тем, что с объемом изменений я мог бы сломать магические числа. Вот упрощенный пример (набор регистров более сложный):
const short mode0 = 0;
const short mode1 = 1;
const short mode2 = 2;
const short state0 = 0;
const short state1 = 4;
const short state2 = 8;
так что вместо:
set_register(5);
у нас есть:
set_register(state1|mode1);
То, что я ищу, это версия времени сборки:
ASSERT(5==(state1|mode1));
Обновить
@Christian, спасибо за быстрый ответ, мне тоже интересен ответ в среде C / non-boost, потому что это код драйвера / ядра.
10 ответов
НОВЫЙ ОТВЕТ:
В моем первоначальном ответе (ниже) мне нужно было иметь два разных макроса для поддержки утверждений в области функций и в глобальной области. Я задавался вопросом, возможно ли придумать единственное решение, которое будет работать в обеих областях.
Мне удалось найти решение, которое работало бы для компиляторов Visual Studio и Comeau с использованием внешних символьных массивов. Но мне удалось найти более сложное решение, которое работает для GCC. Но решение GCC не работает для Visual Studio.:(Но, добавив '#ifdef __ GNUC __', легко выбрать правильный набор макросов для данного компилятора.
Решение:
#ifdef __GNUC__
#define STATIC_ASSERT_HELPER(expr, msg) \
(!!sizeof \ (struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; }))
#define STATIC_ASSERT(expr, msg) \
extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)]
#else
#define STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]
#endif /* #ifdef __GNUC__ */
Вот сообщения об ошибках для STATIC_ASSERT(1==1, test_message);
в строке 22 test.c:
GCC:
line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message'
Visual Studio:
test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts
test.c(22) : see declaration of 'STATIC_ASSERTION__test_message'
Комео:
line 22: error: declaration is incompatible with
"char STATIC_ASSERTION__test_message[1]" (declared at line 22)
ОРИГИНАЛЬНЫЙ ОТВЕТ:
Я делаю что-то очень похожее на то, что делает Шашки. Но я включаю сообщение, которое будет отображаться во многих компиляторах:
#define STATIC_ASSERT(expr, msg) \
{ \
char STATIC_ASSERTION__##msg[(expr)?1:-1]; \
(void)STATIC_ASSERTION__##msg[0]; \
}
И для выполнения чего-либо в глобальной области видимости (вне функции) используйте это:
#define GLOBAL_STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]
Есть статья Ральфа Холли, в которой рассматриваются различные варианты статических утверждений в C.
Он представляет три разных подхода:
- значения регистра переключателя должны быть уникальными
- массивы не должны иметь отрицательных размеров
- деление на ноль для константных выражений
Его вывод для лучшей реализации таков:
#define assert_static(e) \
do { \
enum { assert_static__ = 1/(e) }; \
} while (0)
Вы можете свернуть свое собственное статическое утверждение, если у вас нет доступа к функции статического подтверждения сторонней библиотеки (например, boost):
#define STATIC_ASSERT(x) \
do { \
const static char dummy[(x)?1:-1] = {0};\
} while(0)
Недостатком является, конечно, то, что сообщение об ошибке не будет очень полезным, но, по крайней мере, оно даст вам номер строки.
#define static_assert(expr) \
int __static_assert(int static_assert_failed[(expr)?1:-1])
Его можно использовать где угодно и когда угодно. Я думаю, что это самое простое решение.
Перед использованием внимательно проверьте его с помощью компилятора.
Любой из методов, перечисленных здесь, должен работать, и когда C++0x станет доступным, вы сможете использовать встроенный static_assert
ключевое слово.
Пытаться:
#define STATIC_ASSERT(x, error) \
do { \
static const char error[(x)?1:-1];\
} while(0)
Тогда вы можете написать:
STATIC_ASSERT(a == b, a_not_equal_to_b);
Что может дать вам лучшее сообщение об ошибке (в зависимости от вашего компилятора).
Если у вас есть Boost, то с помощью BOOST_STATIC_ASSERT
это путь Если вы используете C или не хотите получить Boost, вот мой c_assert.h
файл, который определяет (и объясняет работу) нескольких макросов для обработки статических утверждений.
Это немного более запутанно, потому что в коде ANSI C вам нужны 2 разных макроса - один, который может работать в области, где у вас есть объявления, и другой, который может работать в области, где идут нормальные операторы. Есть также небольшая работа, которая заставляет макрос работать в глобальной области видимости или в области блоков, и куча мусора, чтобы гарантировать отсутствие конфликтов имен.
STATIC_ASSERT()
может использоваться в блоке объявления переменной или глобальной области видимости.
STATIC_ASSERT_EX()
может быть среди регулярных заявлений.
Для кода C++ (или кода C99, допускающего смешивание объявлений с операторами) STATIC_ASSERT()
будет работать где угодно.
/*
Define macros to allow compile-time assertions.
If the expression is false, an error something like
test.c(9) : error XXXXX: negative subscript
will be issued (the exact error and its format is dependent
on the compiler).
The techique used for C is to declare an extern (which can be used in
file or block scope) array with a size of 1 if the expr is TRUE and
a size of -1 if the expr is false (which will result in a compiler error).
A counter or line number is appended to the name to help make it unique.
Note that this is not a foolproof technique, but compilers are
supposed to accept multiple identical extern declarations anyway.
This technique doesn't work in all cases for C++ because extern declarations
are not permitted inside classes. To get a CPP_ASSERT(), there is an
implementation of something similar to Boost's BOOST_STATIC_ASSERT(). Boost's
approach uses template specialization; when expr evaluates to 1, a typedef
for the type
::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed<true>) >
which boils down to
::interslice::StaticAssert_test< 1>
which boils down to
struct StaticAssert_test
is declared. If expr is 0, the compiler will be unable to find a specialization for
::interslice::StaticAssert_failed<false>.
STATIC_ASSERT() or C_ASSERT should work in either C or C++ code (and they do the same thing)
CPP_ASSERT is defined only for C++ code.
Since declarations can only occur at file scope or at the start of a block in
standard C, the C_ASSERT() or STATIC_ASSERT() macros will only work there. For situations
where you want to perform compile-time asserts elsewhere, use C_ASSERT_EX() or
STATIC_ASSERT_X() which wrap an enum declaration inside it's own block.
*/
#ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
#define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
/* first some utility macros to paste a line number or counter to the end of an identifier
* this will let us have some chance of generating names that are unique
* there may be problems if a static assert ends up on the same line number in different headers
* to avoid that problem in C++ use namespaces
*/
#if !defined( PASTE)
#define PASTE2( x, y) x##y
#define PASTE( x, y) PASTE2( x, y)
#endif /* PASTE */
#if !defined( PASTE_LINE)
#define PASTE_LINE( x) PASTE( x, __LINE__)
#endif /* PASTE_LINE */
#if!defined( PASTE_COUNTER)
#if (_MSC_VER >= 1300) /* __COUNTER__ introduced in VS 7 (VS.NET 2002) */
#define PASTE_COUNTER( x) PASTE( x, __COUNTER__) /* __COUNTER__ is a an _MSC_VER >= 1300 non-Ansi extension */
#else
#define PASTE_COUNTER( x) PASTE( x, __LINE__) /* since there's no __COUNTER__ use __LINE__ as a more or less reasonable substitute */
#endif
#endif /* PASTE_COUNTER */
#if __cplusplus
extern "C++" { // required in case we're included inside an extern "C" block
namespace interslice {
template<bool b> struct StaticAssert_failed;
template<> struct StaticAssert_failed<true> { enum {val = 1 }; };
template<int x> struct StaticAssert_test { };
}
}
#define CPP_ASSERT( expr) typedef ::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed< (bool) (expr) >) > PASTE_COUNTER( IntersliceStaticAssertType_)
#define STATIC_ASSERT( expr) CPP_ASSERT( expr)
#define STATIC_ASSERT_EX( expr) CPP_ASSERT( expr)
#else
#define C_ASSERT_STORAGE_CLASS extern /* change to typedef might be needed for some compilers? */
#define C_ASSERT_GUID 4964f7ac50fa4661a1377e4c17509495 /* used to make sure our extern name doesn't collide with something else */
#define STATIC_ASSERT( expr) C_ASSERT_STORAGE_CLASS char PASTE( PASTE( c_assert_, C_ASSERT_GUID), [(expr) ? 1 : -1])
#define STATIC_ASSERT_EX(expr) do { enum { c_assert__ = 1/((expr) ? 1 : 0) }; } while (0)
#endif /* __cplusplus */
#if !defined( C_ASSERT) /* C_ASSERT() might be defined by winnt.h */
#define C_ASSERT( expr) STATIC_ASSERT( expr)
#endif /* !defined( C_ASSERT) */
#define C_ASSERT_EX( expr) STATIC_ASSERT_EX( expr)
#ifdef TEST_IMPLEMENTATION
C_ASSERT( 1 < 2);
C_ASSERT( 1 < 2);
int main( )
{
C_ASSERT( 1 < 2);
C_ASSERT( 1 < 2);
int x;
x = 1 + 4;
C_ASSERT_EX( 1 < 2);
C_ASSERT_EX( 1 < 2);
return( 0);
}
#endif /* TEST_IMPLEMENTATION */
#endif /* C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 */
Существует также очень тщательное исследование методов STATIC_ASSERT в Модернизированном C++ проектировании Александреску, ISBN 978-0201704310.
Общий переносимый вариант
#if 5 != (state1|mode1)
# error "aaugh!"
#endif
но это не работает в этом случае, потому что они C-константы, а не #define
s.
Вы можете увидеть ядро Linux BUILD_BUG_ON
макрос для чего-то, что обрабатывает ваш случай:
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
когда condition
это правда, это становится ((void)sizeof(char[-1]))
, что является незаконным и должно произойти сбой во время компиляции, а в противном случае это становится ((void)sizeof(char[1]))
, что просто отлично.
Убедитесь, что вы компилируете с достаточно новым компилятором (например, gcc -std=c11
).
Тогда ваше утверждение просто:
_Static_assert(state1|mode1 == 5, "Unexpected change of bitflags");
#define MODE0 0
#define MODE1 1
#define MODE2 2
#define STATE0 0
#define STATE1 4
#define STATE2 8
set_register(STATE1|STATE1); //set_register(5);
#if (!(5==(STATE1|STATE1))) //MY_ASSERT(5==(state1|mode1)); note the !
#error "error blah blah"
#endif
Это не так элегантно, как однострочное решение MY_ASSERT(expr). Вы можете использовать макропроцессор sed, awk или m4 перед компиляцией кода C, чтобы сгенерировать расширение кода DEBUG MY_ASSERT(expr) на несколько строк или код NODEBUG, который удаляет их для производства.