2016-09-01 3 views
0

J'ai déjà une base de code contenant du code répétitif, avec seulement des différences mineures, des identifiants sérialisables, des index, des tableaux variables.Utilisation de listes X et de directives de préprocesseur pour générer le code C configurable Au moment de la compilation

La base de code est énorme, et certains composants sont activés/désactivés sur la base de simples directives et constantes 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; 
    } 
} 

Parce que les données de ce code fonctionne avec est beaucoup plus compliqué, qu'un simple compteur et 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 de la liste X 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 avec cette est que malgré qu'il soit maintenant une mise en œuvre unifiée, l'introduction de nouveaux composants doit toujours copier -paste, et le filtrage via les constantes précédemment définies (c'est-à-dire: CFG_PROJECT) est maintenant exclu de la logique.


est-il un moyen de minimiser le besoin de copier-coller dans divers endroits dans le code et filtrer en fonction des constantes définies (à savoir CFG_PROJECT)?

+0

Ce code n'est tout simplement pas bien conçu. L'utilisation de 'ifdef's partout est un signe que la fonctionnalité supplémentaire a été verrouillée comme une pensée après coup. Vous devriez regarder le noyau Linux qui réalise une quantité de configuration irréelle entre les fonctionnalités permettant de sélectionner différentes architectures. Oui, il y a 'ifdef's, mais pas autant qu'on pourrait s'y attendre, et pas souvent dans les fichiers C. –

+0

La #define - configuration est en fait quelque chose qui ne pouvait pas être évité, c'est ce dont dépend une plus grande base de code. Il y a eu des demandes fonctionnelles supplémentaires qui ne sont pas mentionnées ici, bien sûr. Pour un exemple simple comme celui-ci, quelque chose de beaucoup plus simple est suggéré. –

+1

Qu'est-ce que cela a à voir avec MISRA? Si vous avez besoin que le code soit conforme à MISRA, vous pouvez oublier tout ce désordre de macro laid. En outre, les commutateurs du compilateur '# ifdef' sont de loin beaucoup plus lisibles que les" x macros "et autres non-sens de méta-programmation. – Lundin

Répondre

0

Filtrer à Contstants prédéfinis au moment de la compilation nécessiterait les directives #if, #ifdef de préprocesseur, etc. mais il n'y a aucun moyen d'utiliser ces #define déclarations AFAIK. Cependant, écrire ceux-ci en dehors des instructions #define est totalement 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 l'ancien peut être compilé dans une liste (des sortes)

#define MACRO_LIST \ 
    Autobot_implementation \ 
    Ultron_implementation 

En fonction des constantes définies les éléments de MACRO_LIST seront soit contenir la définition de la fonction X() (ex: mise en œuvre), ou une constante vide.

Dans la mise en œuvre maintenant ce qui suit peut être utilisé:

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ésumer les composants activés, voir combien de composants sont activés et de se référer à eux dans la mise en œuvre, le jeton de concaténer (##) peut être utilisé en relation avec par exemple 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]; 

Fondamentalement toute variable liée, ID ou la mise en œuvre peut être de cette façon "sérialisé".

S'il vous plaît noter:

Cette solution rend le code plus difficile à comprendre, maintenir et vraiment difficile à déboguer, mais une fois qu'il a été testé et vérifié qu'il élimine d'erreur provenant possibilités de copypasting. Le résultat sera un code beaucoup plus propre, beaucoup plus élégant et beaucoup plus petit que l'alternative.

Il a effectivement diminué le temps de développement de 3 mois à quelques heures dans le code de production.