27 votes

Le compilateur C affirme - comment implémenter?

Je voudrais implémenter un "assert" qui empêche la compilation, plutôt que d'échouer à l'exécution, dans le cas d'erreur.

J'en ai actuellement un défini comme ça, qui fonctionne très bien, mais qui augmente la taille des binaires.

 #define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}
 

Exemple de code (qui ne parvient pas à compiler).

 #define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);
 

Comment puis-je l'implémenter pour qu'il ne génère aucun code (afin de minimiser la taille des binaires générés)?

Merci pour toute aide. NickB

28voto

RBerteig Points 23331

Au moment de la compilation affirmer dans le plus pur standard C est possible, et un peu de préprocesseur la ruse rend son utilisation un aspect tout aussi propre que le moteur d'exécution d'utilisation de l' assert().

La clé du problème est de trouver un concept qui peut être évaluée au moment de la compilation et peut causer une erreur pour certaines valeurs. Une seule réponse est la déclaration d'un tableau ne peut pas avoir une taille négative. À l'aide d'un typedef empêche la répartition de l'espace en cas de succès, et préserve de l'erreur en cas d'échec.

Le message d'erreur lui-même va sibylline de se référer à la déclaration d'une taille négative (GCC dit "la taille de la table foo est négatif"), si vous devez choisir un nom pour le type de tableau qui laisse deviner que cette erreur est vraiment une vérification d'assertion.

Un autre problème à gérer, c'est qu'il est seulement possible d' typedef d'un type particulier de nom une fois dans toute unité de compilation. Ainsi, la macro doit arranger pour chaque utilisation pour obtenir un type unique nom de déclarer.

Ma solution habituelle est d'exiger que la macro avoir deux paramètres. La première est la condition pour affirmer, c'est vrai, et le second est une partie du nom du type déclaré en coulisses. La réponse en plinthe des conseils à l'aide jeton de collage et de l' __LINE__ macro prédéfinie pour former un nom unique, éventuellement, sans avoir besoin d'un argument supplémentaire.

Malheureusement, si la vérification d'assertion est dans un fichier inclus, il peut encore entrer en collision avec un chèque dans le même numéro de ligne dans un deuxième fichier inclus, ou à ce numéro de la ligne dans le fichier source principal. Nous pourrions papier plus que par l'aide de la macro __FILE__, mais elle est définie à une constante de chaîne et il n'y a pas de préprocesseur astuce qui permet de transformer une chaîne de caractères constante de retour dans une partie d'un nom d'identificateur; pour ne pas mentionner que juridique des noms de fichier peut contenir des caractères qui ne sont pas des pièces d'un identificateur.

Donc, je propose le fragment de code suivant:

/** A compile time assertion check.
 *
 *  Validate at compile time that the predicate is true without
 *  generating code. This can be used at any point in a source file
 *  where typedef is legal.
 *
 *  On success, compilation proceeds normally.
 *
 *  On failure, attempts to typedef an array type of negative size. The
 *  offending line will look like
 *      typedef assertion_failed_file_h_42[-1]
 *  where file is the content of the second parameter which should
 *  typically be related in some obvious way to the containing file
 *  name, 42 is the line number in the file on which the assertion
 *  appears, and -1 is the result of a calculation based on the
 *  predicate failing.
 *
 *  \param predicate The predicate to test. It must evaluate to
 *  something that can be coerced to a normal C boolean.
 *
 *  \param file A sequence of legal identifier characters that should
 *  uniquely identify the source file in which this condition appears.
 */
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)

#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];

Une utilisation typique pourrait être quelque chose comme:

#include "CAssert.h"
...
struct foo { 
    ...  /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);

Dans GCC, un échec d'assertion ressemblerait à:

$ gcc-c démonstration.c
la démo.c:32: erreur: la taille de la table `assertion_failed_demo_c_32 " est négative
$

6voto

Stephen C. Steel Points 2869

La suite de COMPILER_VERIFY(exp) macro fonctionne assez bien.

// combiner des arguments (après expansion des arguments)
#define COLLE(a,b) __de la COLLE(a,b)
#define __de la COLLE(a,b) a ## b

#define CVERIFY(expr, msg) typedef char de la COLLE (compiler_verify_, msg) [(expr) ? (+1) : (-1)]

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__)

Il fonctionne aussi bien le C et le C++ et peut être utilisé partout où un typedef serait autorisé. Si l'expression est vraie, il génère un typedef pour un tableau de 1 char (ce qui est sans danger). Si l'expression est fausse, il génère un typedef pour un tableau de -1 caractères, ce qui entraîne généralement un message d'erreur. L'expression donnée comme arugment peut être quelque chose qui donne une constante de compilation (donc les expressions impliquant sizeof() fonctionnent bien). Cela le rend beaucoup plus souple que

#if (expr)
#erreur
#endif

où vous êtes limité à des expressions qui peuvent être évalués par le préprocesseur.

3voto

dreamlax Points 47152

Si votre compilateur définit une macro de préprocesseur comme DEBUG ou NDEBUG, vous pouvez créer quelque chose comme ceci (sinon vous pouvez le configurer dans un Makefile):

 #ifdef DEBUG
#define MY_COMPILER_ASSERT(EXPRESSION)   switch (0) {case 0: case (EXPRESSION):;}
#else
#define MY_COMPILER_ASSERT(EXPRESSION)
#endif
 

Ensuite, votre compilateur n'affirme que pour les versions de débogage.

3voto

ChrisInEdmonton Points 2040

Le meilleur article que j'ai pu trouver sur la statique des assertions dans C est au pixelbeat. Notez que statique affirmations sont ajoutés pour C++ 0X, et peut le faire pour C1X, mais cela ne va pas être pour tout. Je ne sais pas si les macros dans le lien que j'ai donné à accroître la taille de vos fichiers binaires. Je soupçonne qu'ils ne seraient pas, au moins si vous compilez à un niveau raisonnable de l'optimisation, mais votre kilométrage peut varier.

2voto

Welbog Points 32952

Lorsque vous compilez vos binaires finaux, définissez MY_COMPILER_ASSERT pour être vide, afin que sa sortie ne soit pas incluse dans le résultat. Définissez-le uniquement comme vous l'avez pour le débogage.

Mais vraiment, vous n'allez pas pouvoir saisir toutes les affirmations de cette façon. Certains n'ont tout simplement pas de sens au moment de la compilation (comme l'affirmation qu'une valeur n'est pas nulle). Tout ce que vous pouvez faire est de vérifier les valeurs des autres #defines. Je ne sais pas vraiment pourquoi vous voudriez faire ça.

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