Using X-lists and preprocessor directives to generate configurable C Code At compile time

我只是一个虾纸丫 提交于 2019-12-25 08:47:41

问题


I have a codebase already containing repetitive code, with only minor differences, serializable ID-s, indexes, variable arrays.

The codebase is huge, and some components are being activated/deactivated based on simple preprocessor directives and constants(e.g.: #define CFG_PROJECT cfgAutobot, #define CFG_PROJECT cfgUltron, ..etc).

The functionality is effectively the same, but with varying components and conditionals. Example:

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;
    }
}

Because the data this code works with is much more complicated, than a simple counter and integer, involves multiple components of varying sizes, these code parts are much more complicated. However they can be traced back to a common structure.

I was able to apply the X-list technique as follows:

#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;
    }
}

The problem with this is that in spite of it now being a unified implementation, the introduction of new components still needs copy-paste, and filtering via previously defined constants(i.e.: CFG_PROJECT) is now excluded from the logic.


Is there a way to minimize the need of copy-pasting into various places in the code and to filter based on defined constants (i.e. CFG_PROJECT)?


回答1:


Filtering to predefined contstants at compile time would require the prerocessor directives #if, #ifdef, etc.. but there is no way to use these inside #define statements AFAIK.

However writing these outside the #define statements is totally legitimate.

#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

And the former can be compiled into a list(of sorts)

#define MACRO_LIST \
    Autobot_implementation \
    Ultron_implementation

Depending on the defined constants the elements of MACRO_LIST will either contain the X() function definition (i.e.: implementation), or an empty constant.

In the implementation now the following can be used:

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
}

To sum up the activated components, see how many components are activated and to refer to them in the implementation, the concatenate (##) token can be used in relation with e.g. an enumeration definition. Example:

#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];

Basically any related variable, ID or implementation can be "serialized" this way.

Please note:

This solution makes the code harder to comprehend, maintain and really difficult to debug, but once it have been tested and verified it eliminates error possibilites coming from copypasting. The result will be a much cleaner, much more elegant and much smaller codebase, than the alternative.

It actually decreased development time from 3 months to a few hours in production code.



来源:https://stackoverflow.com/questions/39273219/using-x-lists-and-preprocessor-directives-to-generate-configurable-c-code-at-com

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!