Начини за 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 / среда без усилване, защото това е код на драйвер/ядро.


person Community    schedule 06.10.2008    source източник
comment
Има и много задълбочено изследване на STATIC_ASSERT техниките в Modern C++ Design, ISBN 978-0201704310.   -  person jonner    schedule 06.10.2008


Отговори (11)


Има статия от Ralf Holly, която разглежда различни опции за статични твърдения в 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

статично твърдение на Checkout boost

person Christian.K    schedule 06.10.2008
comment
Използвам това навсякъде в нашия код. Дори хващаше хора да правят глупави неща, които биха причинили необяснимо, но голямо хаос веднъж или два пъти. - person Mark Kegel; 06.10.2008

Можете да превъртите свой собствен статичен assert, ако нямате достъп до функция за статично твърдение на библиотека на трета страна (като 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 variable dummy изисква инициализатор Така че промених това на 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 стане достъпен, ще можете да използвате вградения static_assert ключова дума.

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 параметъра, а вторият параметър не е низ. - person BЈовић; 29.02.2016