Способы 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, потому что это код драйвера / ядра.


person Community    schedule 06.10.2008    source источник
comment
Также есть очень тщательное изучение методов STATIC_ASSERT в Modern C ++ Design, ISBN 978-0201704310.   -  person jonner    schedule 06.10.2008


Ответы (11)


Есть статья Ральфа Холли, в которой рассматриваются различные варианты статических утверждений в C.

Он представляет три разных подхода:

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

Его вывод о лучшей реализации таков:

#define assert_static(e) \
    do { \
        enum { assert_static__ = 1/(e) }; \
    } while (0)
person Community    schedule 02.12.2008
comment
Do {...} while (0) позволяет этому макросу работать только внутри функции. Если вы тестируете объявление в верхней части файла вне функции, компилятор выдаст ошибку. Я сократил это до простого enum {assert_static__ = 1 / (e)}, и теперь он работает везде. - person Davide Andrea; 25.09.2016
comment
assert_static__ ... Совет: вызовите эту фиктивную переменную как-нибудь, что намекает на ошибку, например: array_size_is_wrong - person Davide Andrea; 25.09.2016

статическое утверждение для ускорения оформления заказа

person Christian.K    schedule 06.10.2008
comment
Я использую это во всем нашем коде. Он даже поймал людей, делающих глупости, которые один или два раза привели бы к необъяснимым, но серьезным разрушениям. - person Mark Kegel; 06.10.2008

Вы можете свернуть собственное статическое утверждение, если у вас нет доступа к статической функции утверждения сторонней библиотеки (например, boost):

#define STATIC_ASSERT(x) \
    do { \
        const static char dummy[(x)?1:-1] = {0};\
    } while(0)

Обратной стороной является, конечно, то, что сообщение об ошибке не будет очень полезным, но, по крайней мере, оно даст вам номер строки.

person Alex B    schedule 06.10.2008
comment
Хорошая импровизация, спасибо! В моей среде сборки я обнаружил ошибку: Ошибка: # 257: фиктивная переменная const требует инициализатора. Поэтому я изменил ее на const static char dummy [(x)? 1: -1] = {0}; Если вы согласны / обновите это, я помечу это как ответ, еще раз спасибо. - person tonylo; 06.10.2008

#define static_assert(expr) \
int __static_assert(int static_assert_failed[(expr)?1:-1])

Его можно использовать где угодно и когда угодно. Думаю, это самое простое решение.

Перед использованием тщательно протестируйте его с помощью вашего компилятора.

person Community    schedule 22.05.2011
comment
Мне это нравится, хотя для проекта, над которым я работаю, это не годится, потому что настройки моего компилятора будут жаловаться на объявленную, но не используемую функцию. - person Andy Lester; 23.03.2012
comment
@AndyLester: Для этого используется ключевое слово inline или __attribute__((unused)) - person nmichaels; 13.06.2014
comment
Не пишите двойное подчеркивание в собственных идентификаторах - эти имена зарезервированы для реализации для любых целей! - person Toby Speight; 26.04.2018

Любой из перечисленных здесь методов должен работать, и когда C ++ 0x станет доступным, вы сможете использовать встроенный _ 1_ ключевое слово.

person jwfearn    schedule 06.10.2008
comment
C - это не C ++. - person Toby Speight; 26.04.2018

Если у вас есть 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 */
person Michael Burr    schedule 06.10.2008
comment
Зачем вам нужны определения PASTE и PASTE2? Разве мы не можем напрямую использовать x##__LINE__ или x##__COUNTER__? - person Cœur; 20.02.2016
comment
@ Cœur: необходимо правильно обрабатывать вставку значений макроса. См. stackoverflow.com/a/217181/12711 - person Michael Burr; 20.02.2016
comment
Спасибо за ссылку, которая частично объясняет. Но все же двойное косвенное обращение требуется только в том случае, если вы использовали макрос PASTE непосредственно в своем коде. Поскольку PASTE имеет смысл только в других макросах (PASTE_COUNTER, PASTE_LINE или STATIC_ASSERT), второй уровень косвенного обращения PASTE2 кажется бесполезным. - person Cœur; 21.02.2016
comment
Если макрос, вызываемый напрямую, FOO(x) использует оператор вставки токена со своим операндом, x, и он вызывается с макросом в качестве аргумента, то будет вставлено имя макроса, а не значение макроса. Обычно это не то, что нужно. Дополнительное косвенное обращение решает эту проблему. - person Michael Burr; 21.02.2016

Пытаться:

#define STATIC_ASSERT(x, error) \
do { \
    static const char error[(x)?1:-1];\
} while(0)

Тогда вы можете написать:

STATIC_ASSERT(a == b, a_not_equal_to_b);

Что может дать вам более точное сообщение об ошибке (в зависимости от вашего компилятора).

person Andreas Magnusson    schedule 06.10.2008
comment
Ах ... ты тоже меня победил! :-) - person Kevin; 06.10.2008

Распространенный переносной вариант -

#if 5 != (state1|mode1)
#    error "aaugh!"
#endif

но в данном случае это не работает, потому что это константы C, а не #defines.

Вы можете увидеть макрос BUILD_BUG_ON ядра Linux для того, что касается вашего случая:

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

Когда condition истинно, это становится ((void)sizeof(char[-1])), что недопустимо и должно завершиться ошибкой во время компиляции, в противном случае оно становится ((void)sizeof(char[1])), и это нормально.

person ephemient    schedule 06.10.2008
comment
Разработчики ядра заметили, что он не обрабатывает неконстантные выражения так хорошо, как им хотелось бы, но пытается заменить его [например, lkml.org/lkml/2008/8/17/92 и lkml.org/lkml/2008/9/2/170] еще не принято. - person ephemient; 06.10.2008

Убедитесь, что вы компилируете достаточно свежую версию компилятора (например, gcc -std=c11).

Тогда ваше утверждение просто:

_Static_assert(state1|mode1 == 5, "Unexpected change of bitflags");
person Community    schedule 26.04.2018

#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, который удаляет их для производства.

person Community    schedule 15.06.2020

person    schedule
comment
У меня здесь что-то похожее: atalasoft.com/cs/blogs/stevehawley/archive/2007/10/29/ - person plinth; 06.10.2008
comment
Мне нравится то, что вы делаете с параметром msg; Возможно, мне придется добавить эту возможность к моей. Мне также придется протестировать мой на gcc. Интересно, если бы вы изменили «2» на «-1» в объявлении массива условных символов, разве это не привело бы к ошибке в gcc? Тогда вы могли бы избавиться от особого случая gcc. - person Michael Burr; 08.10.2008
comment
Поскольку ваш макрос не соответствует на 100% тому, о чем спрашивают, вам следовало бы добавить несколько примеров. Ваше решение принимает 2 параметра, а 2-й параметр не является строкой. - person BЈовић; 29.02.2016