web-dev-qa-db-fra.com

Assertion statique en C

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?

71
Matt Joiner

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.

71
emsr

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); 
}
  1. 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

  2. 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_...)

  3. 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
77
Nordic Mainframe

cl

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'

11
bobbogo

De Wikipedia :

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

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
4
Tyler

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]
2
BrentNZ

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.

1
Paolo.Bolzoni

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.

1
FredFredFredFred

Parce que:

  1. _Static_assert() est maintenant défini dans gcc pour toutes les versions de C, et
  2. static_assert() est défini dans C++ 11 et versions ultérieures

La macro simple suivante pour STATIC_ASSERT() fonctionne donc dans:

  1. C++:
    1. C++ 11 (g++ -std=c++11) ou ultérieur
  2. C:
    1. gcc -std=c90
    2. gcc -std=c99
    3. gcc -std=c11
    4. 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: 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);
^

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. 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;
}
1
Gabriel Staples

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.

0
melpomene

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
0
jay

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
//
0
user4978854

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 predicate 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++

0
Hashbrown