webentwicklung-frage-antwort-db.com.de

Statische Aussage in C

Was ist der beste Weg, um statische Kompilierungs-Asserts in C (nicht C++) zu erzielen, mit besonderem Schwerpunkt auf GCC?

71
Matt Joiner

Der C11-Standard fügt das Schlüsselwort _Static_assert hinzu.

Dies ist seit gcc-4.6 implementiert :

_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */

Der erste Steckplatz muss ein integraler konstanter Ausdruck sein. Der zweite Slot ist ein konstantes String-Literal, das lang sein kann (_Static_assert(0, L"assertion of Doom!")).

Ich sollte beachten, dass dies auch in aktuellen Versionen von clang implementiert ist.

71
emsr

Dies funktioniert im Funktions- und Nichtfunktionsbereich (aber nicht innerhalb von structs, unions). 

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

STATIC_ASSERT(1,this_should_be_true); 

int main()
{
 STATIC_ASSERT(1,this_should_be_true); 
}
  1. Wenn die Kompilierzeit-Assertion nicht abgeglichen werden konnte, wird von GCC eine fast verständliche Nachricht generiert. sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative

  2. Das Makro könnte oder sollte geändert werden, um einen eindeutigen Namen für die Typedef zu generieren (d. H. Verketten Sie __LINE__ am Ende des static_assert_...-Namens).

  3. Anstelle eines Ternärs könnte dies auch #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1] verwendet werden, was sogar auf dem rostigen alten cc65-Compiler (für 6502 cpu) funktioniert.

UPDATE: Der Vollständigkeit halber hier die Version mit __LINE__ 

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
// token pasting madness:
#define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
#define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
#define COMPILE_TIME_ASSERT(X)    COMPILE_TIME_ASSERT2(X,__LINE__)

COMPILE_TIME_ASSERT(sizeof(long)==8); 
int main()
{
    COMPILE_TIME_ASSERT(sizeof(int)==4); 
}

UPDATE2: GCC-spezifischer Code

GCC 4.3 (ich denke mal) führte die Funktionsattribute "error" und "warning" ein. Wenn ein Aufruf einer Funktion mit diesem Attribut nicht durch die Beseitigung des Deadcodes (oder andere Maßnahmen) beseitigt werden konnte, wird ein Fehler oder eine Warnung generiert. Dies kann verwendet werden, um die Kompilierzeit mit benutzerdefinierten Fehlerbeschreibungen zu bestätigen. Es bleibt zu bestimmen, wie sie im Namespace-Bereich verwendet werden können, ohne auf eine Dummy-Funktion zurückzugreifen:

#define CTC(X) ({ extern int __attribute__((error("assertion failure: '" #X "' not true"))) compile_time_check(); ((X)?0:compile_time_check()),0; })

// never to be called.    
static void my_constraints()
{
CTC(sizeof(long)==8); 
CTC(sizeof(int)==4); 
}

int main()
{
}

Und so sieht es aus: 

$ gcc-mp-4.5 -m32 sas.c 
sas.c: In function 'myc':
sas.c:7:1: error: call to 'compile_time_check' declared with attribute error: assertion failure: `sizeof(int)==4` not true
77

cl

Ich kenne die Frage ausdrücklich erwähnt gcc, aber der Vollständigkeit halber ist hier ein Tweak für Microsoft-Compiler.

Die Verwendung des negativ bemessenen Arrays typedef überzeugt nicht, dass cl einen anständigen Fehler ausgibt. Es sagt nur error C2118: negative subscript. Ein nullbreites Bitfeld ist in dieser Hinsicht besser. Da es sich dabei um eine Typisierung einer Struktur handelt, müssen wir eindeutige Typnamen verwenden. __LINE__ schneidet den Senf nicht ab - es ist möglich, dass COMPILE_TIME_ASSERT() in derselben Zeile in einem Header und in einer Quelldatei enthalten ist, und das Kompilieren wird unterbrochen. __COUNTER__ kommt zur Rettung (und ist seit gcc in 4.3).

#define CTASTR2(pre,post) pre ## post
#define CTASTR(pre,post) CTASTR2(pre,post)
#define STATIC_ASSERT(cond,msg) \
    typedef struct { int CTASTR(static_assertion_failed_,msg) : !!(cond); } \
        CTASTR(static_assertion_failed_,__COUNTER__)

Jetzt

STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)

unter cl ergibt:

fehler C2149: 'static_assertion_failed_use_another_compiler_luke': Das benannte Bitfeld kann keine Breite von Null haben

Gcc gibt auch eine verständliche Nachricht aus:

fehler: Nullbreite für Bitfeld "static_assertion_failed_use_another_compiler_luke"

11
bobbogo

Aus Wikipedia :

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
4
Tyler

Wenn Sie das Makro STATIC_ASSERT () mit __LINE__ verwenden, können Sie Zeilennummernkollisionen zwischen einem Eintrag in einer .c-Datei und einem anderen Eintrag in einer Headerdatei vermeiden, indem Sie __INCLUDE_LEVEL__ angeben.

Zum Beispiel :

/* Trickery to create a unique variable name */
#define BOOST_JOIN( X, Y )      BOOST_DO_JOIN( X, Y )
#define BOOST_DO_JOIN( X, Y )   BOOST_DO_JOIN2( X, Y )
#define BOOST_DO_JOIN2( X, Y )  X##Y
#define STATIC_ASSERT(x)        typedef char \
        BOOST_JOIN( BOOST_JOIN(level_,__INCLUDE_LEVEL__), \
                    BOOST_JOIN(_assert_on_line_,__LINE__) ) [(x) ? 1 : -1]
2
BrentNZ

Der klassische Weg ist die Verwendung eines Arrays:

char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];

Dies funktioniert, da das Array bei einer gültigen Zusicherung die Größe 1 hat und gültig ist. Wenn es jedoch falsch ist, gibt die Größe von -1 einen Kompilierungsfehler an.

Die meisten Compiler zeigen den Namen der Variablen an und zeigen auf den rechten Teil des Codes, wo Sie eventuelle Kommentare zu der Assertion hinterlassen können.

1
Paolo.Bolzoni

Ich würdeNICHTempfehlen, die Lösung mit einer typedef zu verwenden:

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

Die Array-Deklaration mit dem Schlüsselwort typedef wird NICHT garantiert zur Kompilierzeit ausgewertet. Beispielsweise wird der folgende Code im Blockbereich kompiliert:

int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);

Ich würde das stattdessen empfehlen (an C99):

#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]

Aufgrund des static-Schlüsselworts wird das Array zur Kompilierzeit definiert. Beachten Sie, dass diese Assertion nur mit COND funktioniert, die zur Kompilierzeit ausgewertet werden. Es funktioniert nicht mit Bedingungen (d. H. Die Kompilierung schlägt fehl) mit Bedingungen, die auf Werten im Speicher basieren, z. B. Werten, die Variablen zugewiesen sind.

1

Weil:

  1. _Static_assert() ist jetzt in gcc für alle Versionen von C und definiert
  2. static_assert() ist in C++ 11 und höher definiert

Das folgende einfache Makro für STATIC_ASSERT() funktioniert daher in:

  1. C++:
    1. C++ 11 (g++ -std=c++11) oder höher
  2. C:
    1. gcc -std=c90
    2. gcc -std=c99
    3. gcc -std=c11
    4. gcc (kein Standard angegeben)

Definiere STATIC_ASSERT wie folgt:

/* For C++: */
#ifdef __cplusplus
    #ifndef _Static_assert
        #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
    #endif
#endif
/* Now for gcc (C) (and C++, given the define above): */
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")

Jetzt benutze es:

STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed" 

Beispiele:

Getestet in Ubuntu mit gcc 4.8.4:

Beispiel 1: gute gcc Ausgabe (dh: die STATIC_ASSERT() Codes funktionieren, aber die Bedingung war falsch, was zu einem Assert beim Kompilieren führte):

$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: In der Funktion 'main'
static_assert.c: 78: 38: Fehler: Statische Zusicherung fehlgeschlagen: "(1> 2) fehlgeschlagen"
# define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: Anmerkung: in Erweiterung des Makros "STATIC_ASSERT"
STATIC_ASSERT (1> 2);
^

Beispiel 2: gute g++ -std=c++11-Ausgabe (dh: die STATIC_ASSERT()-Codes funktionieren, aber die Bedingung war falsch, was zu einer Assertion beim Kompilieren führte):

$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: In der Funktion 'int main ()'
static_assert.c: 74: 32: Fehler: Statische Zusicherung fehlgeschlagen: (1> 2) fehlgeschlagen
# define _Static_assert static_assert/* static_assert ist Teil von C++ 11 oder neuer * /
^
static_assert.c: 78: 38: Anmerkung: In Erweiterung des Makros "_Static_assert"
# define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: Anmerkung: in Erweiterung des Makros "STATIC_ASSERT"
STATIC_ASSERT (1> 2);
^

Beispiel 3: fehlgeschlagen C++ - Ausgabe (dh: der Assert-Code funktioniert überhaupt nicht richtig, da dies eine Version von C++ vor C++ 11 verwendet) :

$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: Warnung: Der Bezeichner "static_assert" ist ein Schlüsselwort in C++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2);
^
static_assert.c: In der Funktion 'int main ()'
static_assert.c: 78: 99: Fehler: "static_assert" wurde in diesem Bereich nicht deklariert
# define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: Anmerkung: in Erweiterung des Makros "STATIC_ASSERT"
STATIC_ASSERT (1> 2);
^

Vollständige Testergebnisse hier:

/*
static_assert.c
- test static asserts in C and C++ using gcc compiler

Gabriel Staples
4 Mar. 2019 

To be posted in:
1. https://stackoverflow.com/questions/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756
2. https://stackoverflow.com/questions/3385515/static-assert-in-c/7287341#7287341

To compile & run:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert

-------------
TEST RESULTS:
-------------

1. `_Static_assert(false, "1. that was false");` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  NO

2. `static_assert(false, "2. that was false");` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             NO
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    NO
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    NO
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    NO
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES

3. `STATIC_ASSERT(1 > 2);` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES

*/

#include <stdio.h>
#include <stdbool.h>

/* For C++: */
#ifdef __cplusplus
    #ifndef _Static_assert
        #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
    #endif
#endif
/* Now for gcc (C) (and C++, given the define above): */
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")


int main(void)
{
    printf("Hello World\n");

    /*_Static_assert(false, "1. that was false");*/
    /*static_assert(false, "2. that was false");*/

    STATIC_ASSERT(1 > 2);

    return 0;
}
1
Gabriel Staples

Von Perl speziell Perl.h Zeile 3455 (<assert.h> ist im Voraus enthalten):

/* STATIC_ASSERT_DECL/STATIC_ASSERT_STMT are like assert(), but for compile
   time invariants. That is, their argument must be a constant expression that
   can be verified by the compiler. This expression can contain anything that's
   known to the compiler, e.g. #define constants, enums, or sizeof (...). If
   the expression evaluates to 0, compilation fails.
   Because they generate no runtime code (i.e.  their use is "free"), they're
   always active, even under non-DEBUGGING builds.
   STATIC_ASSERT_DECL expands to a declaration and is suitable for use at
   file scope (outside of any function).
   STATIC_ASSERT_STMT expands to a statement and is suitable for use inside a
   function.
*/
#if (defined(static_assert) || (defined(__cplusplus) && __cplusplus >= 201103L)) && (!defined(__IBMC__) || __IBMC__ >= 1210)
/* static_assert is a macro defined in <assert.h> in C11 or a compiler
   builtin in C++11.  But IBM XL C V11 does not support _Static_assert, no
   matter what <assert.h> says.
*/
#  define STATIC_ASSERT_DECL(COND) static_assert(COND, #COND)
#else
/* We use a bit-field instead of an array because gcc accepts
   'typedef char x[n]' where n is not a compile-time constant.
   We want to enforce constantness.
*/
#  define STATIC_ASSERT_2(COND, SUFFIX) \
    typedef struct { \
        unsigned int _static_assertion_failed_##SUFFIX : (COND) ? 1 : -1; \
    } _static_assertion_failed_##SUFFIX Perl_UNUSED_DECL
#  define STATIC_ASSERT_1(COND, SUFFIX) STATIC_ASSERT_2(COND, SUFFIX)
#  define STATIC_ASSERT_DECL(COND)    STATIC_ASSERT_1(COND, __LINE__)
#endif
/* We need this wrapper even in C11 because 'case X: static_assert(...);' is an
   error (static_assert is a declaration, and only statements can have labels).
*/
#define STATIC_ASSERT_STMT(COND)      STMT_START { STATIC_ASSERT_DECL(COND); } STMT_END

Wenn static_assert verfügbar ist (von <assert.h>), wird es verwendet. Andernfalls wird, wenn die Bedingung falsch ist, ein Bitfeld mit einer negativen Größe deklariert, wodurch die Kompilierung fehlschlägt.

STMT_START/STMT_END sind Makros, die jeweils zu do/while (0) erweitert werden.

0
melpomene

Dies funktionierte für einige alte Gcc. Entschuldigung, dass ich vergessen habe, welche Version es war:

#define _cat(x, y) x##y

#define _sassert(exp, ln)\
extern char _cat(SASSERT_, ln)[1]; \
extern char _cat(SASSERT_, ln)[exp ? 1 : 2]

#define sassert(exp) _sassert((exp), __LINE__)

//
sassert(1 == 2);

//
#148 declaration is incompatible with "char SASSERT_134[1]" (declared at line 134)  main.c  /test/source/controller line 134    C/C++ Problem
0
jay

Dies funktioniert mit der Option "Nicht verwendete entfernen". Ich kann eine globale Funktion verwenden, um globale Parameter zu überprüfen.

//
#ifndef __sassert_h__
#define __sassert_h__

#define _cat(x, y) x##y

#define _sassert(exp, ln) \
extern void _cat(ASSERT_WARNING_, ln)(void); \
if(!(exp)) \
{ \
    _cat(ASSERT_WARNING_, ln)(); \
}

#define sassert(exp) _sassert(exp, __LINE__)

#endif //__sassert_h__

//-----------------------------------------
static bool tab_req_set_relay(char *p_packet)
{
    sassert(TXB_TX_PKT_SIZE < 3000000);
    sassert(TXB_TX_PKT_SIZE >= 3000000);
    ...
}

//-----------------------------------------
Building target: ntank_app.elf
Invoking: Cross ARM C Linker
arm-none-eabi-gcc ...
../Sources/Host_if/tab_if.c:637: undefined reference to `ASSERT_WARNING_637'
collect2: error: ld returned 1 exit status
make: *** [ntank_app.elf] Error 1
//
0
user4978854

Für alle, die etwas wirklich grundlegendes und tragbares wollen, aber keinen Zugriff auf C++ 11-Funktionen haben, habe ich genau das geschrieben.
Verwenden Sie STATIC_ASSERT normalerweise (Sie können es zweimal in dieselbe Funktion schreiben, wenn Sie möchten) und verwenden Sie GLOBAL_STATIC_ASSERT außerhalb von Funktionen mit einer eindeutigen Phrase als ersten Parameter.

#if defined(static_assert)
#   define STATIC_ASSERT static_assert
#   define GLOBAL_STATIC_ASSERT(a, b, c) static_assert(b, c)
#else
#   define STATIC_ASSERT(pred, explanation); {char assert[1/(pred)];(void)assert;}
#   define GLOBAL_STATIC_ASSERT(unique, pred, explanation); namespace ASSERTATION {char unique[1/(pred)];}
#endif

GLOBAL_STATIC_ASSERT(first, 1, "Hi");
GLOBAL_STATIC_ASSERT(second, 1, "Hi");

int main(int c, char** v) {
    (void)c; (void)v;
    STATIC_ASSERT(1 > 0, "yo");
    STATIC_ASSERT(1 > 0, "yo");
//    STATIC_ASSERT(1 > 2, "yo"); //would compile until you uncomment this one
    return 0;
}

Erläuterung:
Zuerst wird geprüft, ob Sie die tatsächliche Zusicherung haben, die Sie auf jeden Fall verwenden möchten, wenn sie verfügbar ist.
Wenn dies nicht der Fall ist, wird dies durch das Abrufen Ihres predicate und das Teilen durch sich selbst bestätigt. Das macht zwei Dinge.
Wenn es null ist, id est, ist die Assertion fehlgeschlagen, führt dies zu einer Division durch Null (die Arithmetik wird erzwungen, da versucht wird, ein Array zu deklarieren).
Wenn es nicht Null ist, wird die Array-Größe auf 1 normalisiert. Wenn die Zusicherung bestanden wurde, sollte die Versagung jedoch ohnehin fehlschlagen, da Ihr Prädikat -1 (ungültig) oder 232442 (massive Verschwendung von Speicherplatz, IDK, wenn es optimiert werden sollte) ausgewertet wird.
Für STATIC_ASSERT ist er in geschweifte Klammern eingeschlossen. Dies macht ihn zu einem Block, der die Variable assert abdeckt, dh Sie können ihn viele Male schreiben.
Es wandelt es auch in void um, was eine bekannte Möglichkeit ist, unused variable-Warnungen zu beseitigen.
Für GLOBAL_STATIC_ASSERT wird kein Namespace generiert, anstatt sich in einem Codeblock zu befinden. Namespaces sind außerhalb von Funktionen erlaubt. Ein unique-Bezeichner ist erforderlich, um widersprüchliche Definitionen zu stoppen, wenn Sie diese mehr als einmal verwenden.


Arbeitete für mich an GCC und VS'12 C++

0
Hashbrown