Le compilateur C affirme – comment mettre en œuvre?

Je voudrais implémenter un “assert” qui empêche la compilation, plutôt que d’échouer au moment de l’exécution, dans le cas d’erreur.

J’ai actuellement une définition comme celle-ci, qui fonctionne très bien, mais qui augmente la taille des fichiers binarys.

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

Exemple de code (qui ne comstack pas).

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

Comment puis-je implémenter cela afin qu’il ne génère aucun code (afin de minimiser la taille des fichiers binarys générés)?

Une assertion à la compilation en langage C pur est possible, et un peu de ruse du préprocesseur rend son utilisation aussi nette que celle d’ assert() au moment de l’exécution.

L’astuce consiste à trouver une construction pouvant être évaluée au moment de la compilation et pouvant générer une erreur pour certaines valeurs. Une réponse est la déclaration d’un tableau ne peut pas avoir une taille négative. L’utilisation d’un typedef empêche l’allocation d’espace en cas de succès et préserve l’erreur en cas d’échec.

Le message d’erreur lui-même fera référence de manière cryptique à la déclaration d’une taille négative (GCC dit “la taille du tableau foo est négative”), vous devez donc choisir un nom pour le type de tableau qui indique que cette erreur est vraiment une vérification d’assertion.

Un autre problème à traiter est qu’il n’est possible de saisir un nom de type particulier qu’une seule fois dans une unité de compilation. La macro doit donc organiser pour chaque utilisation un nom de type unique à déclarer.

Ma solution habituelle a été d’exiger que la macro ait deux parameters. Le premier est la condition pour affirmer est vraie, et le second fait partie du nom de type déclaré en coulisses. La réponse par plinthe suggère l’utilisation d’un collage de jetons et de la macro prédéfinie __LINE__ pour former un nom unique, éventuellement sans nécessiter d’argument supplémentaire.

Malheureusement, si la vérification d’assertion est dans un fichier inclus, elle peut toujours entrer en collision avec une vérification au même numéro de ligne dans un deuxième fichier inclus ou à ce numéro de ligne dans le fichier source principal. Nous pourrions __FILE__ sur cela en utilisant la macro __FILE__ , mais il est défini comme une constante de chaîne et il n’existe aucune astuce de préprocesseur qui puisse transformer une constante de chaîne en partie d’un nom d’identifiant; sans oublier que les noms de fichiers légaux peuvent contenir des caractères qui ne font pas partie légale d’un identifiant.

Donc, je proposerais le fragment de code suivant:

 /** A comstack time assertion check. * * Validate at comstack 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 à ceci:

 $ gcc -c demo.c
 demo.c: 32: error: la taille du tableau `assertion_failed_demo_c_32 'est négative
 $

La COMPILER_VERIFY(exp) fonctionne assez bien.

 // combine les arguments (après avoir développé les arguments)
 #define GLUE (a, b) __GLUE (a, b)
 #define __GLUE (a, b) a ## b

 #define CVERIFY (expr, msg) typedef char GLUE (comstackr_verify_, msg) [(expr)?  (+1): (-1)]

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

Cela fonctionne à la fois en C et en C ++ et peut être utilisé partout où un typedef serait autorisé Si l’expression est vraie, elle génère un typedef pour un tableau de 1 caractère (ce qui est inoffensif). Si l’expression est false, elle génère une typedef pour un tableau de -1 caractères, ce qui entraîne généralement un message d’erreur. L’expression donnée sous forme d’arugment peut être n’importe quel élément qui se traduit par une constante de compilation (les expressions impliquant sizeof () fonctionnent donc bien). Cela le rend beaucoup plus flexible que

 #if (expr)
 #Erreur
 #fin si

où vous êtes limité aux expressions pouvant être évaluées par le préprocesseur.

Si votre compilateur définit une macro de préprocesseur telle que DEBUG ou NDEBUG, vous pouvez créer quelque chose comme ceci (sinon, vous pourriez le définir 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 affirme uniquement pour les générations de débogage.

La meilleure description que j’ai pu trouver sur les assertions statiques en C est à pixelbeat . Notez que des assertions statiques sont ajoutées à C ++ 0X et peuvent être intégrées à C1X, mais cela ne va pas durer longtemps. Je ne sais pas si les macros du lien que j’ai donné augmenteront la taille de vos fichiers binarys. J’imagine qu’ils ne le feraient pas, du moins si vous comstackz à un niveau d’optimisation raisonnable, mais votre kilométrage peut varier.

Je sais que vous êtes intéressé par le C, mais jetez un coup d’œil au static_assert de C ++ de boost . (Incidemment, cela deviendra probablement disponible en C ++ 1x.)

Nous avons fait quelque chose de similaire, encore une fois pour C ++:

 #define COMPILER_ASSERT (expr) enum {ARG_JOIN (ComstackrAssertAtLine, __LINE__) = sizeof (char [(expr)? +1: -1])}

Cela ne fonctionne apparemment qu’en C ++. Cet article décrit un moyen de le modifier pour l’utiliser en C.

Lorsque vous comstackz vos fichiers binarys finaux, définissez MY_COMPILER_ASSERT comme vide, afin que sa sortie ne soit pas incluse dans le résultat. Définissez-le uniquement de la manière dont vous l’avez utilisé pour le débogage.

Mais en réalité, vous ne pourrez pas saisir chaque affirmation de cette façon. Certains n’ont tout simplement pas de sens au moment de la compilation (comme l’affirmation selon laquelle 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 tu voudrais faire ça.

Comme Leander l’a dit, des assertions statiques sont ajoutées à C ++ 11, et maintenant elles le sont.

static_assert(exp, message)

Par exemple

 #include "myfile.hpp" static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!") void doStuff(MyClass object) { } 

Voir la page de référence sur ce sujet.

L’utilisation de ‘#error’ est une définition de préprocesseur valide qui provoque l’arrêt de la compilation sur la plupart des compilateurs. Vous pouvez simplement le faire comme ceci, par exemple, pour empêcher la compilation dans le débogage:

 #ifdef DEBUG #error Please don't comstack now #endif 

J’ai trouvé que cela donnait le message d’erreur le moins déroutant pour GCC. Tout le rest avait un suffixe sur une taille négative ou une autre chose déroutante:

 #define STATIC_ASSERT(expr, msg) \ typedef char ______Assertion_Failed_____##msg[1]; __unused \ typedef char ______Assertion_Failed_____##msg[(expr)?1:2] __unused 

exemple d’utilisation:

  unsigned char testvar; STATIC_ASSERT(sizeof(testvar) >= 8, testvar_is_too_small); 

Et le message d’erreur en gcc (Compilateur ARM / GNU C: 6.3.1):

 conflicting types for '______Assertion_Failed_____testvar_is_too_small' 

Eh bien, vous pouvez utiliser les static asserts dans la bibliothèque boost .

Ce que je crois qu’ils font là-bas, c’est définir un tableau.

  #define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)]; 

Si EXPRESSION est vrai, il définit char x[1]; , ce qui est OK. Si false, définit char x[0]; ce qui est illégal.