Peut-on utiliser un bloc de code comme argument d’une macro C?

J’ai un modèle qui est fondamentalement un code standard avec une partie qui varie au milieu

if(condition){ struct Foo m = start_stuff(); { m.foo = bar(1,2); m.baz = 17; } //this part varies end_stuff(); } 

Est-il acceptable de créer une macro prenant ce bloc de code intermédiaire en argument? Les règles d’expansion des macros en C semblent terriblement compliquées, aussi je ne suis pas sûr qu’il n’y ait pas de cas d’arrêt qui pourrait me tracasser à l’avenir (en particulier, je ne comprends pas comment les arguments des macros sont séparés si mon code a des virgules dedans).

 #define MY_MACRO(typ, do_stuff) do { \ if(condition){ \ struct typ m = start_stuff(); \ do_stuff; \ end_stuff(); \ } \ }while(0) //usage MY_MACRO(Foo, { m.foo = bar(1,2); m.baz = 17; }); 

Jusqu’à présent, la seule chose à laquelle j’ai réussi à penser est de faire une break et de continue être capturé si j’utilise des instructions en boucle dans ma macro, ce qui serait un compromis acceptable pour mon cas d’utilisation particulier.

edit: Bien sûr, j’aurais utilisé une fonction si je le pouvais. L’exemple que j’ai utilisé dans cette question est simplifié et ne présente pas les bits qui ne peuvent fonctionner qu’avec la magie des macros.

    Vous pouvez insérer un bloc de code dans un argument de macro, à condition qu’il ne comporte pas de virgule non protégée. Dans votre exemple, la seule virgule de l’argument est gardée, car elle est entourée de parenthèses.

    Notez que seules les parenthèses gardent les virgules. Les crochets ( [] ) et les accolades ( {} ) ne le sont pas.

    Vous pouvez également envisager d’utiliser une macro qui précède votre instruction composée, comme illustré ci-dessous. L’un des avantages de cela est que tous les débogueurs seraient toujours en mesure d’intervenir dans votre instruction composée, ce qui n’est pas le cas avec la méthode compound-statement-as-macro-argument.

     //usage MY_MACRO(Foo, condition) { m.foo = bar(1,2); m.baz = 17; } 

    En utilisant un peu de magie goto (oui, ‘goto’ peut être pervers dans certains cas, mais nous avons peu d’alternatives en C), la macro peut être implémentée comme suit:

     #define CAT(prefix, suffix) prefix ## suffix #define _UNIQUE_LABEL(prefix, suffix) CAT(prefix, suffix) #define UNIQUE_LABEL(prefix) _UNIQUE_LABEL(prefix, __LINE__) #define MY_MACRO(typ, condition) if (condition) { \ struct typ m = start_stuff(); goto UNIQUE_LABEL(enter);} \ if (condition) while(1) if (1) {end_stuff(); break;} \ else UNIQUE_LABEL(enter): 

    Notez que cela a un impact faible sur les performances et l’empreinte lorsque l’optimisation du compilateur est désactivée. En outre, un débogueur semblera revenir à la ligne MY_MACRO lors de l’exécution de l’appel de la fonction end_stuff (), ce qui n’est pas vraiment souhaitable.

    Vous pouvez également utiliser la macro dans une nouvelle scope de bloc pour éviter de polluer votre scope avec la variable ‘m’:

     {MY_MACRO(Foo, condition) { m.foo = bar(1,2); m.baz = 17; }} 

    Bien sûr, l’utilisation de ‘break’ dans une boucle nestede dans l’instruction composée ignorerait le ‘end_stuff ()’. Pour permettre à ceux-ci de casser la boucle environnante et d’appeler toujours ‘end_stuff ()’, je pense qu’il faudrait enfermer l’instruction composée avec un jeton de début et un jeton de fin, comme dans:

     #define MY_MACRO_START(typ, condition) if (condition) { \ struct typ m = start_stuff(); do { #define MY_MACRO_EXIT goto UNIQUE_LABEL(done);} while (0); \ end_stuff(); break; \ UNIQUE_LABEL(done): end_stuff();} MY_MACRO_START(foo, condition) { m.foo = bar(1,2); m.baz = 17; } MY_MACRO_END 

    Notez qu’en raison de la “rupture” dans cette approche, la macro MY_MACRO_EXIT ne serait utilisable qu’à l’intérieur d’une boucle ou d’un commutateur. Vous pouvez utiliser une implémentation plus simple quand vous n’êtes pas dans une boucle:

     #define MY_MACRO_EXIT_NOLOOP } while (0); end_stuff();} 

    J’ai utilisé ‘condition’ comme argument de macro, mais vous pouvez également l’intégrer directement dans la macro si vous le souhaitez.

    Vous pouvez mettre un bloc de code dans une macro, mais vous devez être averti que cela rend le débogage beaucoup plus difficile à l’aide d’un débogueur. À mon humble avis, il vaut mieux écrire une fonction ou couper et coller les lignes de code.

    Qu’en est-il des pointeurs de fonction (et éventuellement inline fonctions inline )?

     void do_stuff_inner_alpha(struct Foo *m) { m->foo = bar(1,2); m->baz = 17; } void do_stuff_inner_beta(struct Foo *m) { m->foo = bar(9, 13); m->baz = 445; } typedef void(*specific_modifier_t)(struct Foo *); void do_stuff(specific_modifier_t func) { if (condition){ struct Foo m = start_stuff(); func(&m); //this part varies end_stuff(); } } int main(int argc, const char *argv[]) { do_stuff(do_stuff_inner_beta); return EXIT_SUCCESS; } 

    “Est-ce que c’est bon?” peut signifier deux choses:

    1. Est-ce que ça marchera? Ici, la réponse est généralement oui, mais il y a des pièges. Comme mentionné par rici , l’un est une virgule non gardée. En gros, rappelez-vous que le développement de macros est une opération copier-coller et que le préprocesseur ne comprend pas le code qu’il copie et le colle.

    2. Est-ce que c’est une bonne idée? Je dirais que la réponse est généralement non. Cela rend votre code illisible et difficile à maintenir. Dans de rares cas, cela peut être meilleur que des alternatives, s’il est bien mis en œuvre, mais c’est l’exception.

    Avant de répondre à votre question “est-il correct d’utiliser une macro”, j’aimerais savoir pourquoi vous souhaitez convertir ce bloc de code en macro. Qu’est-ce que vous essayez de gagner et à quel prix?

    Si le même bloc de code que vous utilisez à plusieurs resockets, il est préférable de le convertir en une fonction, peut-être une fonction inline, et de laisser le compilateur le rendre inline ou non.

    Si vous rencontrez un problème avec crash, le débogage d’une macro est une tâche fastidieuse.