Quel est le meilleur moyen d’obtenir des assertions statiques de temps de compilation en C (et non en C++), avec un accent particulier sur GCC?
La norme C11 ajoute le mot clé _Static_assert
.
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 deuxième emplacement est un littéral de chaîne constant 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.
Cela fonctionne dans les fonctions et les non-fonctions (mais pas dans les structures, les 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 la compilation n’a pas pu être vérifiée, 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 peut ou doit être modifiée pour générer un nom unique pour le typedef (c'est-à-dire concaténer __LINE__
à la fin du nom static_assert_...
)
Au lieu d’un ternaire, ceci pourrait aussi bien être utilisé #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
qui fonctionne même avec le compilateur rouillé olde cc65 (pour le processeur 6502).
UPDATE: 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é. Ceci peut être utilisé pour effectuer des assertions à la 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 d'un 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
Je sais que la question mentionne explicitement gcc, mais pour compléter, voici un compilateur Tweak for Microsoft.
L'utilisation de la matrice de taille négative typedef ne persuade pas cl de cracher une erreur décente. Il dit simplement error C2118: negative subscript
. Un bitfield de largeur nulle se porte mieux à cet égard. Comme cela implique de typer une structure, nous devons utiliser des noms de type uniques. __LINE__
ne coupe pas la moutarde - il est possible d'avoir une COMPILE_TIME_ASSERT()
sur la même ligne dans un en-tête et un fichier source, et votre compilation sera brisée. __COUNTER__
vient à la rescousse (et il est dans gcc depuis la 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__)
À présent
STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
sous cl
donne:
erreur C2149: 'static_assertion_failed_use_another_compiler_luke': la largeur du champ de bit nommé ne peut pas être nulle
Gcc donne aussi un message intelligible:
erreur: largeur nulle 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 );
Si vous utilisez la macro STATIC_ASSERT () avec __LINE__
, il est possible d'éviter les conflits de numéro de ligne entre une entrée d'un fichier .c et une entrée différente d'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 parce que si l'assertion est vraie, le tableau a la taille 1 et il est valide, mais s'il est faux, la taille -1 donne une erreur de compilation.
La plupart des compilateurs affichent le nom de la variable et pointent vers la partie droite du code où vous pouvez laisser des commentaires éventuels sur l'assertion.
Je recommanderaisPASd'utiliser la solution avec une variable typedef
:
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
La déclaration du tableau avec le mot clé typedef
n'est PAS garantie d'être évaluée au moment de la compilation. Par exemple, le code suivant dans la portée du 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 mot clé static
, le tableau sera défini lors de la compilation. Notez que cette assertion ne fonctionnera qu'avec COND
qui sont évalués au moment de la compilation. Cela ne fonctionnera pas (la compilation échouera) avec des conditions basées sur des valeurs en mémoire, telles que des valeurs affectées à des variables.
_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érieuresSTATIC_ASSERT()
fonctionne donc dans:g++ -std=c++11
) ou ultérieurgcc -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: bon sortie gcc
(c'est-à-dire: les codes STATIC_ASSERT()
fonctionnent, mais la condition était fausse, ce qui a provoqué 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: l'assertion statique a échoué: "(1> 2) a échoué"
# define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") a échoué")
^
static_assert.c: 88: 5: remarque: en cours d’expansion de la macro ‘STATIC_ASSERT’
STATIC_ASSERT (1> 2);
^
Exemple 2: good g++ -std=c++11
sortie (c'est-à-dire: les codes STATIC_ASSERT()
fonctionnent, mais la condition était fausse, ce qui a provoqué 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: échec de l'assertion statique: échec (1> 2)
# define _Static_assert static_assert/*static_assert
fait partie de C++ 11 ou version ultérieure * /
^
static_assert.c: 78: 38: remarque: en cours d’expansion de la macro ‘_Static_assert’
# define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") a échoué")
^
static_assert.c: 88: 5: remarque: en cours d’expansion de la macro ‘STATIC_ASSERT’
STATIC_ASSERT (1> 2);
^
Exemple 3: échec Sortie C++ (ie: le code d'assertion ne fonctionne pas du tout correctement car il utilise une version de C++ before 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 étendue
# define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") a échoué")
^
static_assert.c: 88: 5: remarque: en cours d’expansion 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. 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;
}
De Perl, plus précisément Perl.h
ligne 3455 (<assert.h>
est déjà inclus):
/* 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 (à partir de <assert.h>
), il est utilisé. Sinon, si la condition est fausse, un champ de bits de taille négative est déclaré, ce qui entraîne l'échec de la compilation.
STMT_START
/STMT_END
sont des macros étendues à do
/while (0)
, respectivement.
Cela a fonctionné pour un vieux gcc. Désolé d'avoir oublié quelle version c'était:
#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
Cela fonctionne, avec l'option "supprimer les inutilisés". 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
//
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 écrit ce qu'il fallait.
Utilisez STATIC_ASSERT
normalement (vous pouvez l'écrire deux fois dans la même fonction si vous le souhaitez) et utilisez GLOBAL_STATIC_ASSERT
en 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:
Il vérifie d’abord si vous avez la vraie assertion, que vous voudriez certainement utiliser si elle est disponible.
Si vous ne le faites pas, affirme-t-il en obtenant votre pred
icate et en le divisant par lui-même. Cela fait deux choses.
Si c'est zéro, id est, l'assertion a échoué, cela entraînera une erreur de division par zéro (l'arithmétique est forcée car elle tente de déclarer un tableau).
S'il n'est pas nul, la taille du tableau est normalisée à 1
. Donc, si l'assertion est réussie, vous ne voudriez pas qu'elle échoue de toute façon car votre prédicat est évalué à -1
(invalide), ou à être 232442
(énorme perte d'espace, IDK s'il est optimisé).
Pour STATIC_ASSERT
, il est entouré d'accolades, ce qui en fait un bloc, qui étend la variable assert
, ce qui signifie que vous pouvez l'écrire plusieurs fois.
Il la convertit également en void
, méthode connue pour se débarrasser des avertissements unused variable
.
Pour 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 identifiant unique
est requis pour arrêter toute définition en conflit si vous utilisez celle-ci plusieurs fois .
Travaillé pour moi sur GCC et VS'12 C++