Was ist der beste Weg, um statische Kompilierungs-Asserts in C (nicht C++) zu erzielen, mit besonderem Schwerpunkt auf GCC?
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.
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);
}
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
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).
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
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"
Aus Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
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]
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.
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.
_Static_assert()
ist jetzt in gcc für alle Versionen von C und definiertstatic_assert()
ist in C++ 11 und höher definiertSTATIC_ASSERT()
funktioniert daher in:g++ -std=c++11
) oder höhergcc -std=c90
gcc -std=c99
gcc -std=c11
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"
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);
^
/*
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;
}
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.
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
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
//
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 pred
icate 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++