110 votes

Assertion statique en C

Quelle est la meilleure façon d'obtenir des assertions statiques à la compilation en C (pas en C++), en particulier avec GCC ?

0 votes

Pour C11 sur GCC/Clang pour le contrôle d'égalité avec int32_ts, vous pouvez même faire en sorte que le compilateur imprime la valeur incorrecte s'il échoue ! stackoverflow.com/q/53310844/1495449

119voto

emsr Points 4616

La norme C11 ajoute le _Static_assert mot-clé.

C'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 constant de type chaîne de caractères qui peut être long ( _Static_assert(0, L"assertion of doom!") ).

Je dois noter que ceci est également implémenté dans les versions récentes de clang.

9 votes

[...semble être implémenté par gcc, par clang...] Vous pouvez être plus assertive que cela que cela ;-) _Static_assert fait partie de la norme C11 et tout compilateur qui supporte C11, l'aura.

2 votes

Peut-on l'utiliser au niveau du fichier (en dehors de toute fonction) ? Parce que j'obtiens error: expected declaration specifiers or '...' before 'sizeof' pour la ligne static_assert( sizeof(int) == sizeof(long int), "Error!); (Au fait, j'utilise C et non C++).

0 votes

@user10607 Je suis surpris que cela ne fonctionne pas.. Attendez, il manque un guillemet à la fin de votre chaîne d'erreur. Mettez-le et revenez. Cela fonctionne pour moi sur gcc-4.9 : _Static_assert( sizeof(int) == sizeof(long int), "Error!"); Sur mon macine je reçois l'erreur.

105voto

Nordic Mainframe Points 13717

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); 
}
  1. Si l'assertion au moment de la compilation n'a pas pu être satisfaite, 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

  2. La macro pourrait ou devrait être modifiée pour générer un nom unique pour le typedef (c'est-à-dire concaténer __LINE__ à la fin de la static_assert_... nom)

  3. Au lieu d'un ternaire, on pourrait aussi utiliser ceci #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1] qui fonctionne même avec le vieux compilateur 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 crois) a introduit les attributs de fonction "error" et "warning". Si un appel à une fonction avec cet attribut n'a pas pu être éliminé par l'élimination du code mort (ou d'autres mesures), alors une erreur ou un avertissement est généré. Ceci peut être utilisé pour faire des assertions au moment de la compilation avec des descriptions d'échec définies par l'utilisateur. Il reste à déterminer comment elles peuvent être utilisées dans la portée de l'espace de nom 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 voilà à 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

2 votes

Dans Visual Studio, il est simplement indiqué "indice négatif", sans mentionner le nom de la variable...

0 votes

Nordic Mainframe - l'option 3 de votre réponse ne fonctionne pas sur clang.

1 votes

Concernant la dernière solution (spécifique à GCC 4.3+) : Elle est très puissante, car elle peut vérifier tout ce que l'optimiseur peut trouver, mais elle échoue si l'optimisation n'est pas activée. Le niveau d'optimisation minimum ( -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'assert statique un no-op ou un assert d'exécution si __OPTIMIZE__ (et __GNUC__ ) n'est pas défini.

11voto

bobbogo Points 4201

Cl

Je sais que la question mentionne explicitement gcc, mais juste pour être complet, voici une modification pour les compilateurs Microsoft.

L'utilisation du typedef de tableau de taille négative n'est pas convaincante. cl pour cracher une erreur décente. Elle dit juste error C2118: negative subscript . Un champ de bits de largeur nulle est plus efficace à cet égard. Puisque cela implique le typage d'une structure, nous devons vraiment utiliser des noms de types uniques. __LINE__ n'est pas suffisante - 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 interrompue. __COUNTER__ vient à la rescousse (et il est présent 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 binaire nommé ne peut pas avoir une largeur de zéro.

Gcc donne également un message intelligible :

erreur : largeur nulle pour le champ de bits 'static_assertion_failed_use_another_compiler_luke'.

4voto

Tyler Points 1586

Desde Wikipedia :

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

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );

15 votes

Il serait préférable que vous fassiez un lien vers la vraie source : jaggersoft.com/pubs/CVu11_3.html

0 votes

Cela ne fonctionne pas dans gcc 4.6 - il est dit "case label does not reduce to an integer constant". Il y a un point.

0 votes

Vous êtes probablement tous deux passés à autre chose maintenant, mais j'ai fini par écrire le mien (cf. ma réponse ). J'ai utilisé votre lien @MattJoiner pour m'aider

0voto

Hashbrown Points 1888

Pour ceux d'entre vous qui veulent quelque chose de vraiment basique et portable mais qui n'ont pas accès aux fonctionnalités du C++11, j'ai écrit exactement ce qu'il faut.
Utilisez STATIC_ASSERT normalement (vous pouvez l'écrire deux fois dans la même fonction si vous le souhaitez) et utiliser la fonction 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 :
Tout d'abord, il vérifie si vous disposez de l'affirmation réelle, que vous devez absolument utiliser si elle est disponible.<br>Si vous ne le faites pas, il affirme en obtenant votre <code>pred</code> icate, et en le divisant par lui-même. Cela fait deux choses.<br>S'il est égal à zéro, c'est-à-dire que l'assertion a échoué, elle provoquera une erreur de division par zéro (l'arithmétique est forcée car elle essaie de déclarer un tableau).<br>Si elle n'est pas nulle, elle normalise la taille du tableau à <code>1</code> . Donc, si l'assertion a réussi, vous ne voudriez pas qu'elle échoue de toute façon parce que votre prédicat a été évalué à <code>-1</code> (invalide), ou être <code>232442</code> (gaspillage massif d'espace, je ne sais pas si cela peut être optimisé).<br>Pour <code>STATIC_ASSERT</code> il est entouré d'accolades, ce qui en fait un bloc, qui détermine la portée de la variable <code>assert</code> ce qui signifie que vous pouvez l'écrire plusieurs fois.<br>Il le lance aussi à <code>void</code> ce qui est un moyen connu pour se débarrasser de <code>unused variable</code> avertissements.<br>Pour <code>GLOBAL_STATIC_ASSERT</code> au lieu d'être dans un bloc de code, il génère un espace de nom. Les espaces de noms sont autorisés en dehors des fonctions. A <code>unique</code> est nécessaire pour éviter tout conflit de définitions si vous utilisez celui-ci plus d'une fois.


Cela a fonctionné pour moi avec GCC et VS'12 C++.

2 votes

Il n'y a pas d'espace de noms en C.

0 votes

Ah, oups, j'ai mal lu la question. On dirait que je suis venu ici pour chercher une réponse à C++ de toute façon (en regardant la dernière ligne de ma réponse), donc je vais la laisser ici au cas où d'autres feraient la même chose

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X