使用 X-lists 和预处理器指令生成可配置的 C 代码 在编译时

Posted

技术标签:

【中文标题】使用 X-lists 和预处理器指令生成可配置的 C 代码 在编译时【英文标题】:Using X-lists and preprocessor directives to generate configurable C Code At compile time 【发布时间】:2016-09-01 13:43:46 【问题描述】:

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

代码库非常庞大,一些组件正在根据简单的预处理器指令和常量(例如:#define CFG_PROJECT cfgAutobot#define CFG_PROJECT cfgUltron、..etc)进行激活/停用。

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

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)进行过滤?

【问题讨论】:

这段代码只是没有很好的架构。在各处使用ifdefs 表明附加功能是事后才添加的。您应该查看 Linux 内核,它在启用功能和选择不同架构之间实现了不真实的配置。是的,有ifdefs,但没有预期的那么多,而且在 C 文件中也不常见。 #define - 配置实际上是无法避免的,它是更大的代码库所依赖的。当然,这里没有提到一些额外的功能要求。对于像这样的简单示例,建议使用更简单的方法。 这与 MISRA 有什么关系?如果您需要代码符合 MISRA,您可以忘记所有丑陋的宏混乱。此外,#ifdef 编译器开关比“x 宏”和其他此类元编程废话更具可读性。 我不同意,我认为阅读 600 行代码比阅读 5800 行代码更容易。 正如已经指出的那样,首先需要执行这些晦涩的预处理器实践源于糟糕的程序设计。似乎一种简单的继承/多态形式就是解决方案。这样的实现不需要很复杂,只需通过函数指针调用特定的行为就足够了。再说一次,为什么是 MISRA 标签? MISRA 禁止使用类似函数的宏。 【参考方案1】:

在编译时过滤到预定义的常量将需要预处理器指令#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 个月减少到了几个小时。

【讨论】:

以上是关于使用 X-lists 和预处理器指令生成可配置的 C 代码 在编译时的主要内容,如果未能解决你的问题,请参考以下文章

20145205 《信息安全系统设计基础》第5周学习总结

使用预处理器指令生成成员名称?

如何为 C# 生成自定义 StyleCop 规则 - 条件编译预处理器指令

C#预处理器指令

配置中的运行时指令

预处理器指令#define 特定于我的机器