Möglichkeiten zum ASSERT-Ausdrücken zur Build-Zeit in C

Lesezeit: 10 Minuten

Ich räume gerade etwas älteren Code auf, der überall „magische Zahlen“ verwendet, um Hardwareregister zu setzen, und ich würde gerne Konstanten anstelle dieser Zahlen verwenden, um den Code etwas aussagekräftiger zu machen (tatsächlich werden sie den Namen zugeordnet /Werte zur Dokumentation der Register).

Ich mache mir jedoch Sorgen, dass ich mit der Menge der Änderungen die magischen Zahlen brechen könnte. Hier ist ein vereinfachtes Beispiel (der Registersatz ist komplexer):

const short mode0 = 0;
const short mode1 = 1;
const short mode2 = 2;

const short state0 = 0;
const short state1 = 4;
const short state2 = 8;

also statt:

set_register(5);

wir haben:

set_register(state1|mode1);

Was ich suche ist ein Bauzeit Version von:

ASSERT(5==(state1|mode1));

Aktualisieren

@Christian, danke für die schnelle Antwort, ich bin auch an einer Antwort in einer C-/Nicht-Boost-Umgebung interessiert, da dies ein Treiber-/Kernel-Code ist.

  • Es gibt auch eine sehr gründliche Untersuchung der STATIC_ASSERT-Techniken in Alexandrescus Modernes C++-DesignISBN 978-0201704310.

    – Jonner

    6. Oktober 2008 um 14:06 Uhr


Benutzer-Avatar
Kevin

NEUE ANTWORT :

In meiner ursprünglichen Antwort (unten) musste ich zwei verschiedene Makros haben, um Behauptungen in einem Funktionsbereich und im globalen Bereich zu unterstützen. Ich fragte mich, ob es möglich wäre, eine einzige Lösung zu finden, die in beiden Bereichen funktioniert.

Ich konnte eine Lösung finden, die für Visual Studio- und Comeau-Compiler mit externen Zeichenarrays funktionierte. Aber ich konnte eine komplexere Lösung finden, die für GCC funktioniert. Aber die Lösung von GCC funktioniert nicht für Visual Studio. 🙁 Aber durch Hinzufügen eines ‘#ifdef __ GNUC __’ ist es einfach, den richtigen Satz von Makros für einen bestimmten Compiler auszuwählen.

Lösung:

#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__ */

Hier sind die Fehlermeldungen für gemeldet STATIC_ASSERT(1==1, test_message); in Zeile 22 von test.c:

AGB:

line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message'

Visuelles Studio:

test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts
    test.c(22) : see declaration of 'STATIC_ASSERTION__test_message'

Comeau:

line 22: error: declaration is incompatible with
        "char STATIC_ASSERTION__test_message[1]" (declared at line 22)

ORIGINALE ANTWORT :

Ich mache etwas sehr ähnliches wie Checkers. Aber ich füge eine Nachricht hinzu, die in vielen Compilern angezeigt wird:

#define STATIC_ASSERT(expr, msg)               \
{                                              \
    char STATIC_ASSERTION__##msg[(expr)?1:-1]; \
    (void)STATIC_ASSERTION__##msg[0];          \
}

Und um etwas im globalen Bereich (außerhalb einer Funktion) zu tun, verwenden Sie Folgendes:

#define GLOBAL_STATIC_ASSERT(expr, msg)   \
  extern char STATIC_ASSERTION__##msg[1]; \
  extern char STATIC_ASSERTION__##msg[(expr)?1:2]

  • Ich habe hier etwas ähnliches: atalasoft.com/cs/blogs/stevehawley/archive/2007/10/29/…

    – Sockel

    6. Oktober 2008 um 14:21 Uhr

  • Mir gefällt, was Sie mit dem msg-Parameter machen; Möglicherweise muss ich diese Fähigkeit zu meiner hinzufügen. Ich muss meine auch auf gcc testen. Ich frage mich, ob Sie die ‘2’ in Ihrer bedingten char-Array-Deklaration in eine ‘-1’ geändert haben, müsste das nicht einen Fehler auf gcc verursachen? Dann könnten Sie den gcc-Spezialfall loswerden.

    – Michael Burr

    7. Oktober 2008 um 20:09 Uhr

  • Da Ihr Makro nicht zu 100% dem entspricht, was gefragt wird, sollten Sie einige Beispiele hinzufügen. Ihre Lösung benötigt 2 Parameter, und der 2. Parameter ist keine Zeichenfolge.

    – BЈовић

    29. Februar 2016 um 13:48 Uhr

  • Ist dies immer noch der empfohlene Weg, um Behauptungen zur Kompilierungszeit zu erreichen?

    – David Jarvis

    7. Juni um 17:26 Uhr

Es gibt einen Artikel von
Ralf Holle das verschiedene Optionen für statische Asserts in C untersucht.

Er stellt drei unterschiedliche Ansätze vor:

  • Switch-Case-Werte müssen eindeutig sein
  • Arrays dürfen keine negativen Dimensionen haben
  • Division durch Null für konstante Ausdrücke

Sein Fazit für die beste Umsetzung lautet:

#define assert_static(e) \
    do { \
        enum { assert_static__ = 1/(e) }; \
    } while (0)

  • Das “do {… } while(0)” erlaubt diesem Makro, nur innerhalb einer Funktion zu arbeiten. Wenn Sie eine Deklaration am Anfang der Datei außerhalb einer Funktion testen, gibt der Compiler einen Fehler aus. Ich habe das auf “enum {asser_static__ = 1/(e)}” reduziert und jetzt funktioniert es überall.

    – David Andrea

    24. September 2016 um 21:25 Uhr

  • “assert_static__” … Tipp: Nennen Sie diese Dummy-Variable etwas, das auf den Fehler hinweist, zB: array_size_is_wrong

    – David Andrea

    24. September 2016 um 21:26 Uhr

Checkout-Boost statische Behauptung

  • Ich verwende dies in unserem gesamten Code. Es erwischte sogar Leute dabei, dumme Dinge zu tun, die ein- oder zweimal unerklärliches, aber großes Chaos verursacht hätten.

    – Markus Kegel

    6. Oktober 2008 um 15:31 Uhr

Benutzer-Avatar
Alex B

Sie können Ihr eigenes statisches Assert rollen, wenn Sie keinen Zugriff auf eine statische Assert-Funktion einer Drittanbieterbibliothek (wie Boost) haben:

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

Der Nachteil ist natürlich, dass die Fehlermeldung nicht sehr hilfreich sein wird, aber sie gibt Ihnen zumindest die Zeilennummer.

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

Es kann jederzeit und überall verwendet werden. Ich denke, es ist die einfachste Lösung.

Testen Sie es vor der Verwendung sorgfältig mit Ihrem Compiler.

  • Ich mag es, obwohl es für das Projekt, an dem ich arbeite, nicht ausreichen würde, weil sich meine Compilereinstellungen über eine deklarierte, aber nicht verwendete Funktion beschweren würden.

    – Andy Lester

    23. März 2012 um 3:26 Uhr

  • @AndyLester: Das ist es, was die inline Schlüsselwort ist für, oder __attribute__((unused))

    – nmichaels

    13. Juni 2014 um 18:19 Uhr


  • Schreiben Sie keine doppelten Unterstriche in Ihre eigenen Bezeichner – diese Namen sind für die Implementierung reserviert, für jeden Zweck!

    – Toby Speight

    26. April 2018 um 15:48 Uhr

Benutzer-Avatar
jwfear

Jede der hier aufgeführten Techniken sollte funktionieren und sobald C++0x verfügbar wird, können Sie die eingebaute verwenden static_assert Stichwort.

  • Ich mag es, obwohl es für das Projekt, an dem ich arbeite, nicht ausreichen würde, weil sich meine Compilereinstellungen über eine deklarierte, aber nicht verwendete Funktion beschweren würden.

    – Andy Lester

    23. März 2012 um 3:26 Uhr

  • @AndyLester: Das ist es, was die inline Schlüsselwort ist für, oder __attribute__((unused))

    – nmichaels

    13. Juni 2014 um 18:19 Uhr


  • Schreiben Sie keine doppelten Unterstriche in Ihre eigenen Bezeichner – diese Namen sind für die Implementierung reserviert, für jeden Zweck!

    – Toby Speight

    26. April 2018 um 15:48 Uhr

Benutzer-Avatar
Michael Burr

Wenn Sie Boost haben, dann verwenden Sie BOOST_STATIC_ASSERT ist der Weg zu gehen. Wenn Sie C verwenden oder Boost nicht erhalten möchten, hier ist mein c_assert.h -Datei, die einige Makros definiert (und deren Funktionsweise erklärt), um statische Behauptungen zu verarbeiten.

Es ist ein bisschen komplizierter, als es sein sollte, weil Sie im ANSI-C-Code zwei verschiedene Makros benötigen – eines, das in dem Bereich arbeiten kann, in dem Sie Deklarationen haben, und eines, das in dem Bereich arbeiten kann, in dem normale Anweisungen ausgeführt werden. Es gibt auch ein bisschen Arbeit, damit das Makro im globalen Bereich oder im Blockbereich funktioniert, und eine Menge Mist, um sicherzustellen, dass es keine Namenskollisionen gibt.

STATIC_ASSERT() kann im Variablendeklarationsblock oder im globalen Gültigkeitsbereich verwendet werden.

STATIC_ASSERT_EX() kann zu den regulären Anweisungen gehören.

Für C++-Code (oder C99-Code, der Deklarationen gemischt mit Anweisungen erlaubt) STATIC_ASSERT() wird überall funktionieren.

/*
    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 */

  • Warum brauchen Sie PASTE- und PASTE2-Definitionen? Können wir nicht direkt verwenden x##__LINE__ oder x##__COUNTER__?

    – Coeur

    20. Februar 2016 um 7:40 Uhr

  • @Cœur: Es ist notwendig, das Einfügen von Makrowerten richtig zu handhaben. Siehe stackoverflow.com/a/217181/12711

    – Michael Burr

    20. Februar 2016 um 18:21 Uhr

  • Danke für den Link, das erklärt teilweise. Trotzdem ist eine doppelte Umleitung nur erforderlich, wenn Sie die verwenden PASTE Makro direkt in Ihrem Code. Wie PASTE ist nur innerhalb anderer Makros sinnvoll (PASTE_COUNTER, PASTE_LINE oder STATIC_ASSERT), die zweite Indirektionsebene PASTE2 scheint nutzlos.

    – Coeur

    21. Februar 2016 um 1:52 Uhr

  • Bei einem direkt aufgerufenen Makro FOO(x) verwendet den Token-Einfügeoperator mit seinem Operanden, x, und es wird mit einem Makro als Argument aufgerufen, dann wird der Makroname eingefügt, nicht der Wert des Makros. Das ist in der Regel nicht erwünscht. Die zusätzliche Umleitung löst dieses Problem.

    – Michael Burr

    21. Februar 2016 um 2:16 Uhr

1354680cookie-checkMöglichkeiten zum ASSERT-Ausdrücken zur Build-Zeit in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy