web-dev-qa-db-fra.com

Éviter les avertissements de variables inutilisés lors de l'utilisation de assert () dans une version Release

Parfois, une variable locale est utilisée dans le seul but de la vérifier dans un assert (), comme ceci -

int Result = Func();
assert( Result == 1 );

Lors de la compilation du code dans une version Release, assert () s étant généralement désactivé, ce code peut générer un avertissement concernant le résultat défini, mais jamais lu.

Une solution de contournement possible est -

int Result = Func();
if ( Result == 1 )
{
    assert( 0 );
}

Mais cela nécessite trop de dactylographie, n'est pas facile à regarder et fait en sorte que la condition soit toujours vérifiée (oui, le compilateur peut optimiser le contrôle, mais quand même).

Je cherche une autre façon d'exprimer cette assert () d'une manière qui ne provoque pas l'avertissement, mais qui reste simple à utiliser et évite de changer la sémantique de assert ().

(Désactiver l'avertissement en utilisant un # pragma dans cette région de code n'est pas une option, et baisser les niveaux d'avertissement pour le faire disparaître n'est pas une option non plus ...).

51
Hexagon

Nous utilisons une macro pour indiquer spécifiquement quand quelque chose n'est pas utilisé:

#define _unused(x) ((void)(x))

Ensuite, dans votre exemple, vous auriez:

int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy

Ainsi, (a) la production est réussie et (b) il est évident dans le code que la variable est inutilisée par sa conception , même si elle n’a pas été oubliée. Ceci est particulièrement utile lorsque les paramètres d'une fonction ne sont pas utilisés.

48
Graeme Perrow

Je ne serais pas en mesure de donner une meilleure réponse que celle-ci, qui résout ce problème, et bien d'autres encore:

Astuces C++ stupides: Aventures in assert

#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif
22
hcpizzi

Vous pouvez créer une autre macro qui vous permette d'éviter d'utiliser une variable temporaire:

#ifndef NDEBUG
#define Verify(x) assert(x)
#else
#define Verify(x) ((void)(x))
#endif

// asserts that Func()==1 in debug mode, or calls Func() and ignores return
// value in release mode (any braindead compiler can optimize away the comparison
// whose result isn't used, and the cast to void suppresses the warning)
Verify(Func() == 1);
9
Adam Rosenfield
int Result = Func();
assert( Result == 1 );

Cette situation signifie qu'en mode de publication, vous voulez vraiment:

Func();

Mais Func est non vide, c’est-à-dire qu’il retourne un résultat, c’est-à-dire qu’il s’agit d’un requête.

Vraisemblablement, en plus de renvoyer un résultat, Func modifie quelque chose (sinon, pourquoi se donner la peine de l'appeler et de ne pas utiliser son résultat?), C'est-à-dire qu'il s'agit d'une commande _.

Selon le principe de séparation commande-requête} _ (1), Func ne devrait pas être une commande et une requête en même temps. En d'autres termes, les requêtes ne doivent pas avoir d'effets secondaires, et le "résultat" des commandes doit être représenté par les requêtes disponibles sur l'état de l'objet.

Cloth c;
c.Wash(); // Wash is void
assert(c.IsClean());

Est mieux que

Cloth c;
bool is_clean = c.Wash(); // Wash returns a bool
assert(is_clean);

Le premier ne vous donne aucun avertissement de votre genre, le second le fait.

Donc, en bref, ma réponse est: n'écris pas un code comme celui-ci :)

Update (1): _ Vous avez demandé des références sur le principe de séparation commande-requête. Wikipedia est plutôt informatif. J'ai lu sur cette technique de conception dans Construction de logiciels orientée objet, 2e édition de Bertrand Meyer.

(Update (2):} _ j_random_hacker commente "OTOH, chaque fonction" commande "f() qui retournait une valeur auparavant doit maintenant définir une variable last_call_to_f_succeeded ou similaire". Ceci n’est vrai que pour les fonctions qui ne promettent rien dans leur contrat, c’est-à-dire les fonctions qui pourraient "réussir" ou non, ou un concept similaire. Avec Conception par contrat, un nombre pertinent de fonctions auront postconditions, donc après "Empty ()", l'objet sera "IsEmpty ()" et après "Encode ()" la chaîne de message sera "IsEncoded ()", sans vérification. De la même manière, et de manière symétrique, vous n'appelez pas une fonction spéciale "IsXFeasible ()" avant chaque appel à une procédure "X ()"; parce que vous savez généralement, par conception, que vous remplissez les conditions préalables de X au moment de votre appel.

8
Daniel Daranas

Vous pouvez utiliser:

Check( Func() == 1 );

Et implémentez votre fonction Check (bool) comme vous le souhaitez. Il peut soit utiliser assert, soit générer une exception particulière, écrire dans un fichier journal ou sur la console, avoir des implémentations différentes pour le débogage et la publication, ou une combinaison des deux.

3
Jem

C’est une mauvaise utilisation de assert, à mon humble avis. Assert n'est pas conçu comme un outil de rapport d'erreur, il est destiné à affirmer des conditions préalables. Si Result n'est pas utilisé ailleurs, ce n'est pas une condition préalable.

2
anon

Avec le C++ récent, je dirais simplement:

[[maybe_unused]] int Result = Func();
assert( Result == 1 );

Voir https://en.cppreference.com/w/cpp/language/attributes/maybe_unused pour plus de détails sur cet attribut.

Comparé à l’astuce (void)Result, je l’aime mieux, car vous décorez directement la déclaration de variable, vous n’ajoutez pas simplement quelque chose après coup.

2
Gathar

Le plus simple est de déclarer/assigner ces variables seulement si les assertions vont exister. La macro NDEBUG est spécifiquement définie si les assertions ne seront pas seront effectuées (cela uniquement parce que -DNDEBUG est un moyen pratique de désactiver le débogage, je pense), donc cette copie modifiée de la réponse de @ Jardel devrait fonctionner ( cf. commentaire de @AdamPeterson sur cette réponse):

#ifndef NDEBUG
int Result =
#endif
Func();
assert(Result == 1);

ou, si cela ne convient pas à vos goûts, toutes sortes de variantes sont possibles, par exemple. ce:

#ifndef NDEBUG
int Result = Func();
assert(Result == 1);
#else
Func();
#endif

En général, avec ce type de matériel, veillez à ce qu'il ne soit jamais possible de construire différentes unités de traduction avec différents états de macro NDEBUG - en particulier re. assertions ou autre contenu conditionnel dans les fichiers d'en-tête publics. Le danger est que vous, ou les utilisateurs de votre bibliothèque, instanciez accidentellement une définition d'une fonction inline différente de celle utilisée dans la partie compilée de la bibliothèque, en violant discrètement la règle de définition one et en rendant le comportement d'exécution indéfini.

2
andybuckley

Vous devez déplacer l'assertion à l'intérieur de la fonction avant la ou les valeurs renvoyées. Vous savez que la valeur de retour n'est pas une variable locale non référencée.

De plus, il est plus logique d’être de toute façon dans la fonction, car elle crée une unité autonome qui a ses propres conditions préalables et postérieures.

Il est fort probable que si la fonction renvoie une valeur, vous devriez quand même effectuer une sorte de vérification d'erreur en mode validation sur cette valeur de retour. Donc, il ne devrait pas être une variable non référencée pour commencer.

Modifier, mais dans ce cas, la condition de publication doit être X (voir les commentaires):

Je suis fortement en désaccord avec ce point, on devrait pouvoir déterminer la post-condition à partir des paramètres d'entrée et si c'est une fonction membre, n'importe quel état d'objet. Si une variable globale modifie la sortie de la fonction, celle-ci doit être restructurée.

1
Brian R. Bondy

La plupart des réponses suggèrent d'utiliser l'astuce static_cast<void>(expression) dans les constructions Release pour supprimer l'avertissement, mais il s'agit en fait d'une valeur non optimale si vous souhaitez réellement effectuer des vérifications Debug- uniquement. Les objectifs d'une macro d'assertion en question sont les suivants:

  1. Effectuer des vérifications en mode Debug
  2. Ne rien faire en mode Release
  3. N'émettre aucun avertissement dans tous les cas

Le problème, c’est que l’approche par le vide n’atteint pas le deuxième objectif. Bien qu'il n'y ait aucun avertissement, l'expression que vous avez transmise à votre macro d'assertion restera toujours évaluée. Si vous, par exemple, faites simplement une vérification de variable, ce n'est probablement pas un gros problème. Mais que se passe-t-il si vous appelez une fonction dans votre vérification d'assertion telle que ASSERT(fetchSomeData() == data); (ce qui est très courant dans mon expérience)? La fonction fetchSomeData() sera toujours appelée. Cela peut être simple et rapide ou non.

Ce dont vous avez vraiment besoin n’est pas seulement la suppression d’avertissements, mais peut-être plus important encore - non-évaluation de l’expression de contrôle debug-only. Ceci peut être réalisé avec une astuce simple que j'ai tirée d'une bibliothèque spécialisée Assert :

void myAssertion(bool checkSuccessful)
{
   if (!checkSuccessful)
    {
      // debug break, log or what not
    }
}

#define DONT_EVALUATE(expression)                                    \
   {                                                                 \
      true ? static_cast<void>(0) : static_cast<void>((expression)); \
   }

#ifdef DEBUG
#  define ASSERT(expression) myAssertion((expression))
#else
#  define ASSERT(expression) DONT_EVALUATE((expression))
#endif // DEBUG

int main()
{
  int a = 0;
  ASSERT(a == 1);
  ASSERT(performAHeavyVerification());

  return 0;
}

Toute la magie est dans la macro DONT_EVALUATE. Il est évident qu'au moins logiquement l'évaluation de votre expression n'est jamais nécessaire à l'intérieur. Pour renforcer cela, la norme C++ garantit qu’une seule des branches d’opérateur conditionnel sera évaluée. Voici la citation:

5.16 Opérateur conditionnel [expr.cond]

(expression-logique ou expression?: expression-affectation} _

Groupe d'expressions conditionnelles de droite à gauche. La première expression est convertie contextuellement en bool. Elle est évaluée et si elle est vraie, le résultat de l'expression conditionnelle est la valeur de la deuxième expression, sinon celle de la troisième expression. Une seule de ces expressions est évaluée.

J'ai testé cette approche dans GCC 4.9.0, version 3.8.0, VS2013 Update 4, VS2015 Update 4 avec les niveaux d'alerte les plus sévères. Dans tous les cas, il n'y a pas d'avertissements et l'expression de contrôle n'est jamais évaluée dans Release build (en fait, le tout est totalement optimisé). Gardez bien à l'esprit qu'avec cette approche, vous allez avoir des problèmes très rapidement si vous mettez des expressions qui ont des effets secondaires dans la macro d'assertion, bien que ce soit une très mauvaise pratique en premier lieu.

De plus, je m'attendrais à ce que les analyseurs statiques puissent avertir du "résultat d'une expression est toujours constant" (ou quelque chose comme ça) avec cette approche. J'ai testé cela avec des outils d'analyse statique clang, VS2013, VS2015 et je n'ai reçu aucun avertissement de ce type.

1
Sergey Nikitin

Vous utilisez certainement une macro pour contrôler votre définition d'assertion, telle que "_ASSERT". Donc, vous pouvez faire ceci:

#ifdef _ASSERT 
int Result =
#endif /*_ASSERT */
Func();
assert(Result == 1);
1
Jardel Lucca

Si ce code est à l'intérieur d'une fonction, agissez et renvoyez le résultat:

bool bigPicture() {

   //Check the results
   bool success = 1 != Func();
   assert(success == NO, "Bad times");

   //Success is given, so...
   actOnIt();

   //and
   return success;
}
0
Michael

Avec C++ 17, nous pouvons faire:

[[maybe_unused]] int Result = Func();

bien que cela implique un peu de frappe supplémentaire par rapport à une substitution d'assertion. Voir cette réponse .

Remarque: Ceci est ajouté car il s'agit du premier hit Google pour "variable inutilisée d'assertion c ++".

0
Jeevaka
// Value is always computed.  We also call assert(value) if assertions are
// enabled.  Value is discarded either way.  You do not get a warning either
// way.  This is useful when (a) a function has a side effect (b) the function
// returns true on success, and (c) failure seems unlikely, but we still want
// to check sometimes.
template < class T >
void assertTrue(T const &value)
{
  assert(value);
}

template < class T >
void assertFalse(T const &value)
{ 
  assert(!value);
}
0
Trade-Ideas Philip

Je voudrais utiliser ce qui suit:

#ifdef _DEBUG
#define ASSERT(FUNC, CHECK) assert(FUNC == CHECK)
#else
#define ASSERT(FUNC, CHECK)
#endif

...

ASSERT(Func(), 1);

De cette façon, pour la compilation, le compilateur n'a même pas besoin de produire de code pour l'assertion.

0
Donotalo
int Result = Func();
assert( Result == 1 );
Result;

Cela fera que le compilateur cessera de se plaindre du résultat non utilisé.

Mais vous devriez penser à utiliser une version d’assert qui fait quelque chose de utile au moment de l’exécution, comme des erreurs de description de journal dans un fichier pouvant être récupéré à partir de l’environnement de production.

0
John Dibling