使用 X 列表和预处理程序指令在编译时生成可配置的 C 代码

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

我有一个代码库已经包含重复代码,只有细微差别、可序列化 ID、索引、变量数组。

代码库庞大,一些组件 activated/deactivated 基于简单的预处理器指令和常量(例如:#define CFG_PROJECT cfgAutobot#define CFG_PROJECT cfgUltron、..等)。

功能实际上是相同的,但具有不同的组件和条件。示例:

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

因为这段代码处理的数据比简单的计数器和整数要复杂得多,涉及多个不同大小的组件,所以这些代码部分要复杂得多。然而,它们可以追溯到一个共同的结构。

我能够按如下方式应用 X-list 技术:

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

这个的问题是,尽管现在已经统一实现了,但是引入新的组件还是需要复制粘贴,通过之前定义的常量(即: CFG_PROJECT) 现在已从逻辑中排除。


有没有一种方法可以最大限度地减少复制粘贴到代码中不同位置的需要,并根据定义的常量(即 CFG_PROJECT)进行过滤?

在编译时过滤预定义的常量需要预处理器指令 #if#ifdef 等。但是无法在 #define 语句 AFAIK 中使用这些指令。

但是在 #define 语句之外写这些是完全合法的。

#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

而前者可以编译成一个列表(各种)

#define MACRO_LIST \
    Autobot_implementation \
    Ultron_implementation

根据定义的常量,MACRO_LIST 的元素将包含 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);\
        }
            MACRO_LIST
        #undef X
}

为了总结激活的组件,查看有多少组件被激活并在实现中引用它们,连接 (##) 令牌可以用于与例如枚举定义。示例:

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

基本上任何相关的变量、ID 或实现都可以用这种方式“序列化”。

请注意:

此解决方案使代码更难理解、维护并且确实难以调试,但一旦经过测试和验证,它就消除了复制粘贴带来的错误可能性。结果将是一个比替代方案更干净、更优雅、更小的代码库。

它实际上将生产代码的开发时间从 3 个月减少到几个小时。