Quelle est la meilleure façon d'obtenir des assertions statiques au moment de la compilation en C (et non en C ++), avec un accent particulier sur GCC?
c
gcc
assert
compile-time
static-assert
Matt Joiner
la source
la source
_Static_assert
fait partie du standard C11 et tout compilateur qui supporte C11 l'aura.error: expected declaration specifiers or '...' before 'sizeof'
pour la lignestatic_assert( sizeof(int) == sizeof(long int), "Error!);
(j'utilise C pas C ++ d'ailleurs)_Static_assert( sizeof(int) == sizeof(long int), "Error!");
Sur mon macine, j'obtiens l'erreur.error: expected declaration specifiers or '...' before 'sizeof'
ANDerror: expected declaration specifiers or '...' before string constant
(il fait référence à la"Error!"
chaîne) (aussi: je compile avec -std = c11. Lorsque vous placez la déclaration dans une fonction, tout fonctionne bien (échoue et réussit comme prévu))_Static_assert
non le C ++static_assert
. Vous devez `#include <assert.h> pour obtenir la macro static_assert.Cela fonctionne dans la portée des fonctions et des non-fonctions (mais pas à l'intérieur des structures, des 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); }
Si l'assertion de temps de compilation n'a pas pu être mise en correspondance, alors un message presque intelligible est généré par GCC
sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
La macro pourrait ou devrait être modifiée pour générer un nom unique pour le typedef (c'est-
__LINE__
à- dire concaténer à la fin dustatic_assert_...
nom)Au lieu d'un ternaire, cela pourrait également être utilisé, ce
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
qui fonctionne même sur le compilateur rusty olde cc65 (pour le processeur 6502).MISE À JOUR: Par souci d'exhaustivité, voici la version avec
__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: code spécifique à GCC
GCC 4.3 (je suppose) a introduit les attributs de fonction "erreur" et "avertissement". Si un appel à une fonction avec cet attribut n'a pas pu être éliminé par l'élimination du code mort (ou d'autres mesures), une erreur ou un avertissement est généré. Cela peut être utilisé pour faire des assertions de compilation avec des descriptions d'échec définies par l'utilisateur. Il reste à déterminer comment ils peuvent être utilisés dans la portée de l'espace de noms sans recourir à une fonction factice:
#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() { }
Et voici à quoi ça ressemble:
$ 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
la source
-Og
) peut souvent être suffisant pour que cela fonctionne, cependant, et ne devrait pas interférer avec le débogage. On peut envisager de faire de l'assertion statique une assertion no-op ou d'exécution si__OPTIMIZE__
(et__GNUC__
) n'est pas défini.__LINE__
version de gcc 4.1.1 ... avec parfois des ennuis lorsque deux en-têtes différents en ont un sur la même ligne numérotée!cl
Je sais que la question mentionne explicitement gcc, mais juste pour être complet, voici un ajustement pour les compilateurs Microsoft.
L'utilisation du tableau de taille négative typedef ne persuade pas cl de cracher une erreur décente. Cela dit simplement
error C2118: negative subscript
. Un champ de bits de largeur nulle est meilleur à cet égard. Comme cela implique de taper une structure, nous devons vraiment utiliser des noms de type uniques.__LINE__
ne coupe pas la moutarde - il est possible d'avoir unCOMPILE_TIME_ASSERT()
sur la même ligne dans un en-tête et un fichier source, et votre compilation s'arrêtera.__COUNTER__
vient à la rescousse (et il est dans gcc depuis 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__)
Maintenant
STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
sous
cl
donne:Gcc donne également un message intelligible:
la source
De Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
la source
Je ne recommanderais PAS d' utiliser la solution en utilisant un
typedef
:#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
La déclaration de tableau avec le
typedef
mot clé n'est PAS garantie d'être évaluée au moment de la compilation. Par exemple, le code suivant dans la portée de bloc sera compilé:int invalid_value = 0; STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
Je recommanderais plutôt ceci (sur C99):
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
En raison du
static
mot - clé, le tableau sera défini au moment de la compilation. Notez que cette assertion fonctionnera uniquement avec ceuxCOND
qui sont évalués au moment de la compilation. Cela ne fonctionnera pas (c'est-à-dire que la compilation échouera) avec des conditions basées sur des valeurs en mémoire, telles que des valeurs affectées à des variables.la source
Si vous utilisez la macro STATIC_ASSERT () avec
__LINE__
, il est possible d'éviter les conflits de numéros de ligne entre une entrée dans un fichier .c et une entrée différente dans un fichier d'en-tête en incluant__INCLUDE_LEVEL__
.Par exemple :
/* 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]
la source
La méthode classique utilise un tableau:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
Cela fonctionne car si l'assertion est vraie, le tableau a la taille 1 et elle est valide, mais si elle est fausse, la taille -1 donne une erreur de compilation.
La plupart des compilateurs afficheront le nom de la variable et pointeront vers la partie droite du code où vous pourrez laisser d'éventuels commentaires sur l'assertion.
la source
#define STATIC_ASSERT()
macro de type générique et fournir des exemples plus génériques et des exemples de sortie de compilateur à partir de vos exemples génériques en utilisantSTATIC_ASSERT()
vous donnerait beaucoup plus de votes positifs et rendrait cette technique plus logique, je pense.Depuis Perl, en particulier la
perl.h
ligne 3455 (<assert.h>
incluse au préalable):/* 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
Si
static_assert
est disponible (de<assert.h>
), il est utilisé. Sinon, si la condition est fausse, un champ de bits avec une taille négative est déclaré, ce qui provoque l'échec de la compilation.STMT_START
/STMT_END
sont des macros s'étendant versdo
/while (0)
, respectivement.la source
Car:
_Static_assert()
est maintenant défini dans gcc pour toutes les versions de C, etstatic_assert()
est défini dans C ++ 11 et versions ultérieuresLa macro simple suivante pour
STATIC_ASSERT()
fonctionne donc dans:g++ -std=c++11
) ou version ultérieuregcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(pas de std spécifié)Définissez
STATIC_ASSERT
comme suit:/* 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")
Maintenant, utilisez-le:
STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed"
Exemples:
Testé sous Ubuntu avec gcc 4.8.4:
Exemple 1: bonne
gcc
sortie (c'est-à-dire: lesSTATIC_ASSERT()
codes fonctionnent, mais la condition était fausse, provoquant une assertion à la compilation):Exemple 2: bonne
g++ -std=c++11
sortie (ie: lesSTATIC_ASSERT()
codes fonctionnent, mais la condition était fausse, provoquant une assertion à la compilation):Exemple 3: échec de la sortie C ++ (c'est-à-dire: le code d'assertion ne fonctionne pas du tout correctement, car il utilise une version de C ++ antérieure à C ++ 11):
Résultats complets du test ici:
/* static_assert.c - test static asserts in C and C++ using gcc compiler Gabriel Staples 4 Mar. 2019 To be posted in: 1. /programming/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756 2. /programming/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; }
En relation:
la source
static_assert
macro dedansassert.h
?static_assert()
n'est pas du tout disponible en C. Voir aussi ici: en.cppreference.com/w/cpp/language/static_assert - cela montre qu'ilstatic_assert
existe "(depuis C ++ 11)". La beauté de ma réponse est que cela fonctionne dans le C90 de gcc et les versions ultérieures, ainsi que dans tout C ++ 11 et versions ultérieures, au lieu de seulement dans C ++ 11 et versions ultérieures, commestatic_assert()
. Aussi, qu'est-ce qui est compliqué dans ma réponse? Ce n'est que quelques#define
s.static_assert
est défini en C depuis C11. C'est une macro qui se développe_Static_assert
. en.cppreference.com/w/c/error/static_assert . De plus, le contraste avec votre réponse_Static_assert
n'est pas disponible dans c99 et c90 dans gcc (uniquement dans gnu99 et gnu90). Ceci est conforme à la norme. En gros, vous faites beaucoup de travail supplémentaire, cela n'apporte des avantages que si compilé avec gnu90 et gnu99 et qui rend le cas d'utilisation réel insignifiant.Pour ceux d'entre vous qui veulent quelque chose de vraiment basique et portable mais qui n'ont pas accès aux fonctionnalités de C ++ 11, j'ai juste écrit la chose.
Utilisez
STATIC_ASSERT
normalement (vous pouvez l'écrire deux fois dans la même fonction si vous le souhaitez) et utilisez enGLOBAL_STATIC_ASSERT
dehors des fonctions avec une phrase unique comme premier paramètre.#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; }
Explication:
Tout d'abord, il vérifie si vous avez la véritable assertion, que vous voudriez certainement utiliser si elle est disponible.
Si vous ne le faites pas, il affirme en obtenant votre
pred
glace et en la divisant par elle-même. Cela fait deux choses.Si c'est zéro, id est, l'assertion a échoué, cela provoquera une erreur de division par zéro (l'arithmétique est forcée car elle essaie de déclarer un tableau).
S'il n'est pas égal à zéro, il normalise la taille du tableau à
1
. Donc, si l'assertion est réussie, vous ne voudriez pas qu'elle échoue de toute façon parce que votre prédicat est évalué à-1
(invalide), ou à232442
(perte massive d'espace, IDK s'il était optimisé).Car
STATIC_ASSERT
il est entouré d'accolades, cela en fait un bloc, qui couvre la variableassert
, ce qui signifie que vous pouvez l'écrire plusieurs fois.Il le lance également
void
, ce qui est un moyen connu de se débarrasser desunused variable
avertissements.Car
GLOBAL_STATIC_ASSERT
, au lieu d'être dans un bloc de code, il génère un espace de noms. Les espaces de noms sont autorisés en dehors des fonctions. Ununique
identifiant est requis pour arrêter toute définition en conflit si vous l'utilisez plusieurs fois.A travaillé pour moi sur GCC et VS'12 C ++
la source
Cela fonctionne, avec le jeu d'options "supprimer inutilisé". Je peux utiliser une fonction globale pour vérifier les paramètres globaux.
// #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 //
la source
Cela a fonctionné pour certains vieux gcc. Désolé d'avoir oublié de quelle version il s'agissait:
#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
la source