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?
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?
Réponses:
La norme C11 ajoute le _Static_assert
mot - clé.
Ceci est implémenté depuis gcc-4.6 :
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
Le premier emplacement doit être une expression constante intégrale. Le second slot est un littéral de chaîne constante qui peut être long ( _Static_assert(0, L"assertion of doom!")
).
Je dois noter que cela est également implémenté dans les versions récentes de clang.
error: expected declaration specifiers or '...' before 'sizeof'
pour la ligne static_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'
AND error: 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 du static_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
-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!
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 un COMPILE_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:
erreur C2149: 'static_assertion_failed_use_another_compiler_luke': le champ de bits nommé ne peut pas avoir une largeur nulle
Gcc donne également un message intelligible:
erreur: largeur zéro pour le champ de bits 'static_assertion_failed_use_another_compiler_luke'
De Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
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 ceux COND
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.
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 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.
#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 utilisant STATIC_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 vers do
/ while (0)
, respectivement.
_Static_assert()
est maintenant défini dans gcc pour toutes les versions de C, et static_assert()
est défini dans C ++ 11 et versions ultérieuresSTATIC_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"
Testé sous Ubuntu avec gcc 4.8.4:
Exemple 1: bonne gcc
sortie (c'est-à-dire: les STATIC_ASSERT()
codes fonctionnent, mais la condition était fausse, provoquant une assertion à la compilation):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: Dans la fonction 'main'
static_assert.c: 78: 38: erreur: échec de l'assertion statique: "(1> 2) échoué"
#define STATIC_ASSERT (test_for_true ) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: note: dans le développement de la macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Exemple 2: bonne g++ -std=c++11
sortie (ie: les STATIC_ASSERT()
codes fonctionnent, mais la condition était fausse, provoquant une assertion à la compilation):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: Dans la fonction 'int main ()'
static_assert.c: 74: 32: erreur: l'assertion statique a échoué: (1> 2) échoué
#define _Static_assert static_assert / *static_assert
fait partie de C ++ 11 ou version ultérieure * /
^
static_assert.c: 78: 38: note: dans le développement de la macro '_Static_assert'
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: note: dans le développement de la macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
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):
$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: avertissement: l'identificateur 'static_assert' est un mot-clé en C ++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2 );
^
static_assert.c: Dans la fonction 'int main ()'
static_assert.c: 78: 99: erreur: 'static_assert' n'a pas été déclaré dans cette portée
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true " ) a échoué ")
^
static_assert.c: 88: 5: note: dans le développement de la macro '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. /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;
}
static_assert
macro dedans assert.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'il static_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, comme static_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 en GLOBAL_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 des unused 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. Un unique
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 ++
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
//
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
_Static_assert
fait partie du standard C11 et tout compilateur qui supporte C11 l'aura.