Utilisation de directives X-lists et de préprocesseur pour générer du code C configurable au moment de la compilation

J’ai déjà une base de code contenant du code répétitif, avec seulement des différences mineures, des ID-s sérialisables, des index, des tableaux de variables.

La base de code est énorme et certains composants sont activés / désactivés sur la base de directives et de constantes simples du préprocesseur (par exemple: #define CFG_PROJECT cfgAutobot , #define CFG_PROJECT cfgUltron , ..etc).

La fonctionnalité est effectivement la même, mais avec des composants et des conditions variables. Exemple:

 int somedata; int somecounter; void main_loop(){ #if(CFG_PROJECT == cfgAutobot) if(someInterface() == 1){ somedata = some_other_interface(); } #endif #if(CFG_PROJECT == cfgUltron) if(third_if() > 0){ someCounter++; } else { someCounter = 0; } #endif } void query_data(int selector){ if(False){ /* Dummy block */ } #if(CFG_PROJECT == cfgUltron) else if(selector == 1){ return somedata; } #endif #if(CFG_PROJECT == cfgAutobot) else if(selector == 2){ return someCounter; } #endif else{ return Err_code; } } 

Comme les données avec lesquelles ce code fonctionne sont beaucoup plus compliquées qu’un simple compteur et un nombre entier, implique plusieurs composants de différentes tailles, ces parties de code sont beaucoup plus compliquées. Cependant, ils peuvent être rattachés à une structure commune.

J’ai pu appliquer la technique X-list comme suit:

 #define Ultron_implementation X(var_ultron, (someInterface() == 1), update_function_1, selector_id_1) #define Autobot_implementation X(var_autobot, (third_if() > 0), update_function_2, selector_id_2) /* (Please note, that this is a simplified example, in the actual code there are much more components, but the `main_loop` implementation can be traced back to a few update functions) */ void update_function_1(int var, int selector) { if(selector == 1){ var++; }else{ var = 0; } } void update_function_2(int var, int selector) { if(selector == 1){ var = some_other_interface(); }else{ /* Nothing to do */ } } #define X(var_name,condition,func_name,sel_id) int var_name; Ultron_implementation Autobot_implementation #undef X void main_loop(){ #define X(var_name,condition,func_name,sel_id) \ if(condition){ \ func_name(var_name, true);\ }else{ \ func_name(var_name, false);\ } Ultron_implementation Autobot_implementation #undef X } void query_data(int selector){ if(False){ /* Dummy block */ } #define X(var_name,condition,func_name,sel_id) \ else if(selector == sel_id){ \ return var_name;\ } Ultron_implementation Autobot_implementation #undef X else{ return Err_code; } } 

Le problème, c’est que, même s’il s’agit maintenant d’une implémentation unifiée, l’introduction de nouveaux composants nécessite toujours un copier-coller et que le filtrage via des constantes définies précédemment ( CFG_PROJECT ) est maintenant exclu de la logique.


Existe-t-il un moyen de minimiser le besoin de copier-coller à divers endroits dans le code et de filtrer en fonction de constantes définies (par exemple, CFG_PROJECT )?

    Filtrer sur les constantes prédéfinies au moment de la compilation nécessiterait les directives de pré-traitement #if , #ifdef , etc., mais il n’ya aucun moyen d’utiliser ces instructions #define intérieur de AFAIK.

    Cependant, écrire ces #define dehors des déclarations #define est tout à fait légitime.

     #if(CFG_PROJECT == cfgAutobot) #define Autobot_implementation X(var_autobot, (third_if() > 0), update_function_2, selector_id_1) #else #define Autobot_implementation #endif #if(CFG_PROJECT == cfgUltron) #define Ultron_implementation X(var_autobot, (third_if() > 0), update_function_2, selector_id_2) #else #define Ultron_implementation #endif 

    Et le premier peut être compilé dans une liste (de sortes)

     #define MACRO_LIST \ Autobot_implementation \ Ultron_implementation 

    Selon les constantes définies, les éléments de MACRO_LIST contiendront soit la définition de la fonction X() (c’est-à-dire: implementation), soit une constante vide.

    Dans la mise en œuvre, les éléments suivants peuvent être utilisés:

     void main_loop(){ #define X(var_name,condition,func_name,sel_id) \ if(condition){ \ func_name(var_name, true);\ }else{ \ func_name(var_name, false);\ } MACRO_LIST #undef X } 

    Pour récapituler les composants activés, voir le nombre de composants activés et y faire référence dans l’implémentation, le jeton concaténation ( ## ) peut être utilisé en relation, par exemple, avec une définition d’énumération. Exemple:

     #define X(var_name,condition,func_name,sel_id) var_name ## index, tyepdef enum{ MACRO_LIST components_end }component_index; #undef X some_struct COMPONENT_FLAGS[components_end]; 

    De manière générale, toute variable, identifiant ou implémentation associé peut être “sérialisé” de cette façon.

    Notez s’il vous plaît:

    Cette solution rend le code plus difficile à comprendre et à maintenir et très difficile à déboguer, mais une fois testé et vérifié, il élimine les possibilités d’erreur provenant du copypasting. Le résultat sera un code beaucoup plus propre, beaucoup plus élégant et beaucoup plus petit que l’alternative.

    En fait, le temps de développement est passé de 3 mois à quelques heures dans le code de production.