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
$