12
votes

Est-ce que cette abuse macro?

J'ai été ingénierie inverse du code et je suis tombé sur ce ...

/*  Read in an 4                */
    ret_val = read(fd, &input_integer, sizeof(int32));

    CHECK_FREAD(ret_val, "reading an 4");
    CHECK_FEOF(ret_val, "reading an 4");

    if (input_integer != 4)
    {
        return(DCD_BADFORMAT);
    }

    /*  Read in the number of atoms         */
    ret_val = read(fd, &input_integer, sizeof(int32));
    *N = input_integer;

    CHECK_FREAD(ret_val, "reading number of atoms");
    CHECK_FEOF(ret_val, "reading number of atoms");


6 commentaires

Cette macro n'imprime pas un message d'erreur car il devrait en fonction de sa description dans les commentaires. Est-ce un code complet?


Oui, c'est le code complet. Et à Chrisf, voir l'exemple que j'ai ajouté.


Et pourquoi l'argument "MSG" n'est-il pas utilisé? Les commentaires ne correspondent pas à la mise en œuvre, ce qui signifie qu'au moins un est buggy, mais probablement tous les deux. Il serait préférable d'écrire un tad_t coché_fread_fread (refuge de mémoire tampon, Taille_t éléments, Taille_t numitems, fichier * FP, Const Char * msg) Fonction et utilisation de manière systématique. Il est également intéressant de spéculer pourquoi il vérifie -1; Fread () retourne 0 ou un nombre court sur l'erreur - et le type de retour est Taille_t qui exclut une valeur de retour de -1 pour presque tous les objectifs.


Mais est-ce considéré comme abusé de qc? C'est myy question. @Jonathan Je ne sais pas pourquoi ils n'utilisent pas msg ... oubli? paresseux? confusion? vous choisissez! Mais tu as absolument raison, c'est étrange ....


Ils ont probablement utilisé l'attribut MSG lors du test du code et ils ont emporté le code d'impression avant de la libérer. J'ai d'abord pensé à cela comme un moyen d'insérer des commentaires dans le code, mais pourquoi ne pas utiliser honnête / * commentaires * / à la place?


Abus vs mauvaise utilisation? Difficile de dire ... puisque MSG est inutilisé, il est quelque peu inutile. Clairement, les commentaires sur Fread () ne sont pas germes to lire () . Puisque nous ne pouvons pas voir check_feof (), nous ne pouvons pas dire ce que cela fait. Cependant, il me semble que une fonction simple de gérer les deux conditions serait meilleure: elle pourrait envelopper la fonction lue () (appel système) et séparer le statut d'erreur de tout le reste: si ((retval = enveloppé enroulée (FD, et INPUT_Integer, Tailleof (int32), "Lecture d'un 4")! = DCD_NOERROR) Retournez Reval; . Et si le test de 4 est omniprésent, je l'envelopperais encore plus loin. Je pense que c'est la maltraitance macro.


8 Réponses :


22
votes

Parce que ce n'est pas la même chose. Si vous mettez la même chose dans une fonction, vous aurez une fonction courte qui renvoie dcd_badwrite si x est égal -1. La macro en question revient toutefois de la fonction contenant . Exemple: xxx

serait développé à: xxx

si, cependant, il s'agissait d'une fonction, la fonction extérieure ( foobar < / Code>) Ignorerait volontiers le résultat et retournerait 0.

une macro qui ressemble tellement à une fonction, mais se comporte différemment de manière cruciale, est une majeure no-no OMI. Je dirais oui, c'est est abus macro.


4 commentaires

Quelle est l'alternative s'ils lisent une tonne de valeurs et veulent éliminer le code répétitif? Y a-t-il un bon moyen de faire ce qu'ils font (voir mon poste édité pour un exemple de cas)? À propos de la seule chose que je puisse penser à Porthand, il devrait avoir divisé le processus de lecture en différentes parties, comme c'est très long, car le fichier a des sections différentes ...


En C ++, exceptions. Placez le code dans une petite fonction qui, au lieu d'abuser des valeurs de retour pour signaler une défaillance, se lancent sur une erreur. Ensuite, dans l'utilisation du code, mettez la chose entière dans un essayer / attrape et renvoyer dcd_badwrite à partir du attrape . Si c'est uni clair c, alors SETJMP - la manipulation des erreurs de style est probablement une meilleure solution. Et puis, peut-être que le programme hypothétique serait préférable d'utiliser une boucle en premier lieu.


@Jason: Je ne pense tout simplement pas que c'est légitime de vouloir éliminer le code répétitif ici. Surtout une fois que vous avez expliqué le fait que tous les messages sont complètement redondants, leur code "post-élimination" n'est même pas plus court que si (retval! = -1) retourner dcd_badread . Ce n'est certainement pas plus facile d'écrire ou de comprendre.


Dire ne pas utiliser une macro dans le cas seulement que sa peine d'utiliser (où vous avez besoin de substitution textuelle, pas une fonction) est un peu ridicule.



8
votes

Ceci ne peut pas être fait en tant que fonction car le retour est de retour à partir de la fonction d'appel, pas du bloc dans la macro.

C'est le genre de chose qui donne à la macros un mauvais nom, car il cache une logique de contrôle vitale.


1 commentaires

En C ++, il faut utiliser "lancer" pour ce type de chose. En C, une macro sensiblement nommée est souvent la meilleure alternative. Voir ma réponse complète pour plus d'informations.



0
votes

Eh bien, si vous définissez une fonction courte, vous devez taper plus de caractères sur chaque site d'appel. I.e.

if (check_fread(X, msg)) return DCD_BADREAD;


0 commentaires

0
votes

Dans les limites de l'espace, il a été mis en œuvre, il a peut-être été bien (bien qu'il soit généralement fronçonné en C ++).

La chose la plus criante à ce sujet qui m'inquiète, c'est que certains développeurs nouvellement introduits pourraient voir cela et penser à faire xxx

qui est évidemment faux. < P> En outre, il semble que msg n'est utilisé que si vous consommez / attendu dans DCD_BADREDAD qui aggrave la situation ...


3 commentaires

Les développeurs nouvellement introduits feront des erreurs stupides, comme utiliser la valeur de retour d'une fonction (ou le résultat d'une expression) sans vous soucier de rechercher ce que c'est réellement. Pas beaucoup de point en essayant de rendre compte de cela.


@Steve: D'autre part, les présentant des macros particulièrement confuses semble méchant et contre-productif. En particulier lorsque la macro est mal formée et la documentation trompeuse.


Vrai, mais que ce soit une bonne macro ou une mauvaise macro ou une mauvaise (et j'accepte que c'est un mauvais), il convient de dire que la même chose de toute macro qui ne se développe pas pour donner une expression: "Si Quelqu'un écrit si (macro (...)) {...} else {...} , alors cela ira mal ".



2
votes

Que ce soit ou non, une abus est une question de goût. Mais je vois des problèmes principaux avec celui-ci

  1. La documentation est complètement faux
  2. La mise en œuvre est très dangereuse en raison du si et un possible Dangling sinon problème
  3. la nommée ne suggère pas que c'est est un retour préliminaire de la Fonction environnante

    donc c'est définitivement très mauvais style.


0 commentaires

2
votes

Si la macro est dans un fichier source et utilisée uniquement dans ce fichier, je le trouve un peu moins offensant que si elle est désactivée dans une en-tête quelque part. Mais je n'aime pas beaucoup une macro qui revient, (surtout une documentée em> pour mettre fin à l'application et revenir en réalité), et toujours moins celle qui revient conditionnellement, car elle le rend assez facile à créer La fuite de mémoire suivante: xxx pré>

Si je crois que la documentation, je n'ai aucun motif de suspecter que ce code fuit la mémoire chaque fois que la lecture échoue. Même si la documentation était correcte, je préfère que le flux de contrôle dans C soit éventuellement évident, ce qui signifie pas caché par les macros. Je préférerais également que mon code soit vaguement cohérent entre les cas où j'ai des ressources pour nettoyer et étudier où je ne le fais pas, plutôt que d'utiliser la macro pour un mais pas l'autre. Donc, la macro n'est pas à mon goût même si ce n'est pas définitivement abusif. P>

faire ce que dit la documentation, je préférerais écrire quelque chose comme: p> xxx pré>

avec vérifier code> être une fonction. Ou utilisez un affirmer code>. Bien sûr, une affirmation ne fait que quelque chose si NDEBUG n'est pas défini, il n'est donc pas bon de la manipulation des erreurs si vous planifiez des constructions distinctes de débogage et de libération. P>

faire ce que la macro fait réellement, je voudrais Préfère ignorer la macro entièrement et écrire: p>

if (retval == -1) return DCD_BADREAD;


3 commentaires

Très probablement, lors du débogage, il est utile d'activer le code qui enregistre la valeur en msg et renvoie. Pendant la production, le code de journalisation est inutile. Quitter la macro en place permet de refaire la journalisation si elle est nécessaire.


@supercat: La documentation décrit donc le comportement de débogage pour lequel aucun code n'existe dans le fichier, mais que le programmateur de débogage doit être mis en œuvre et insérer si nécessaire? Au lieu du comportement de production pour lequel le code existe? Ensuite, oui, c'est sans aucun doute un abus colossal ;-)


Très probablement, le code de journalisation existait à un moment donné dans le passé, mais a été supprimé. Ma préférence aurait été d'utiliser une macro imbriquée pour la pièce de journalisation et d'utiliser un drapeau logging_enable pour définir une macro imbriquée pour connecter quelque chose ou ne rien faire.



5
votes

Je suis sur la clôture de savoir s'il faut appeler une telle macro * abus *, mais je dirai que ce n'est pas une bonne idée.

Je dirais que en général, vous devriez éviter d'utiliser retour dans une macro. Il est difficile de créer une macro qui gère le retour correctement sans effectuer le flux de contrôle de la fonction qui l'appelle maladroit ou difficile à comprendre. Vous ne voulez certainement pas "accidentellement" de retourner d'une fonction sans le réaliser. Celui-ci peut être ennuyeux de déboguer.

plus, cette macro peut causer des problèmes avec le flux de code en fonction de la manière dont il est utilisé. Par exemple, le code xxx

ne ferait pas ce que vous penseriez que ce serait (le sinon devient associé au si à l'intérieur de la macro). La macro doit être clôturée dans {} pour vous assurer qu'il ne modifie pas de conditionnels environnants.

En outre, le deuxième argument n'est pas utilisé. Le problème évident ici est la mise en œuvre ne correspondant pas à la documentation. Le problème caché est lorsque la macro est invoquée comme ceci: xxx

Vous vous attendez à ce que le pointeur passe à l'élément de tableau suivant après l'appel de la macro. Cependant, le deuxième paramètre n'est pas utilisé, l'incrément n'arrive jamais. C'est une autre raison pour laquelle les déclarations avec des effets secondaires causent des problèmes dans les macros.

au lieu d'utiliser une macro pour faire tout cela, pourquoi ne pas écrire une fonction pour encapsuler le lire et L'erreur vérifie? Il peut retourner zéro sur le succès ou un code d'erreur. Si le code de traitement des erreurs est commun entre tous les blocs lisez , vous pouvez faire quelque chose comme ceci: xxx

aussi longtemps que tout votre Appels vers Read_Helper Attribuer leur valeur de retour à RET , vous pouvez réutiliser le même bloc de code de traitement des erreurs dans toute la fonction. Votre fonction print_error_message_for_code prendrait simplement un code d'erreur comme entrée et utilisez un commutateur ou un tableau pour imprimer un message d'erreur qui correspond à celui-ci.

J'avoue que beaucoup de gens ont peur de goto , mais réutilisant le code de traitement des erreurs courantes au lieu d'une série de blocs imbriqués et de variables de condition peuvent être un cas d'utilisation approprié pour celui-ci. Il suffit de le garder propre et lisible (une étiquette par fonction, et conservez le code de manipulation d'erreur simple).


0 commentaires

1
votes

Je considérerais l'exemple d'abus de macro pour deux raisons: (1) Le nom de la macro ne précise pas ce qu'il fait, notamment qu'il pourrait revenir; (2) La macro n'est pas syntaxiquement équivalente à une déclaration. Le code comme xxx

échouera. Mon changement préféré: xxx


0 commentaires