【问题标题】:Is there a way to substitute C macros for normal C code有没有办法用 C 宏代替普通 C 代码
【发布时间】:2011-06-15 05:33:06
【问题描述】:

我想在 C 文件上运行工具 x 并获取后期宏代码。 (如果我们只能做类似宏的功能就更好了)。我知道gcc -E,但这也将所有内容都包含在一个大文件中。

基本上我想为重复代码使用一些 C 宏,但不希望最终代码包含任何宏,因为项目不赞成它们。

【问题讨论】:

  • 不,正如我提到的 gcc -E 确实包含文件。我想要一个在自动重新格式化后可用的结果文件。
  • 我的意思是,你在问题中写了gcc -F,我想你的意思是gcc -E...:-)
  • 关于宏可以替换多少函数,这里讨论过:Macros faking functions

标签: c macros


【解决方案1】:

使用您选择的脚本语言,注释掉所有#includes,然后运行gcc -E -Wp,-P,-C,-CC foo.c,然后取消注释#includes。或者你可以用一些不以#开头的字符串替换#include ...例如include#@include;可能性是无止境。使用@ 而不是# 的方法使您可以完全控制哪些预处理器指令可以扩展和不扩展...用@ 编写您不想扩展的指令,然后脚本运行gcc -E 然后将 @ 更改为 #。但是,我认为最好反过来做,使用特殊标记(例如,@)来指示您的可扩展宏。然后脚本会将前导#s 转换为其他内容(例如HIDE#)并将标记(例如@)转换为#,运行gcc -E,然后转换HIDE#(或其他) 回到#

-Wp 指定预处理器选项。 -P 表示不生成行指令,-C 表示不删除 cmets,-CC 表示不删除宏生成的 cmets - 这意味着代码生成宏中的 cmets 将保留在输出。要确定所有可用的预处理器选项(有很多,大部分不感兴趣),运行gcc -Wp,--help anyfile.c ...这就是我想出这个答案的方法(在第一次运行gcc --help 找到-Wp 选项之后)。 (知道如何发现事情比知道事情更重要。)

【讨论】:

    【解决方案2】:

    如何在代码中的 #include 列表之后放置一个分隔符,以便您可以手动摆脱包含文件扩展,但在运行 gcc -E 后保持宏扩展完好无损?

    类似:

    #include <one>
    #include <two>
    void delete_everything_above_and_put_includes_back(); // delimeter
    #define MACRO(X) ...
    //rest of the code
    

    我不知道有什么工具可以扩展宏但不扩展 #includes...

    【讨论】:

    • 这不起作用,因为它扩展了头文件中定义的所有宏,而不仅仅是本地宏。有一种更简单的方法......请参阅我的答案。
    • @Jim Hmmm... 我假设宏是在头文件中定义的,但话又说回来 - 您可以完全删除 #include 行,并在完成后将它们放回去,并获得相同的结果,而无需扩展不需要的宏。我个人不太喜欢命令行参数,越少越好,反正运行脚本后要编辑文件,所以无所谓。
    • @littleadv 处理文本以隐藏#s 比删除行然后将它们放回要容易得多。 “我个人不喜欢命令行参数”——你需要参数来让预处理器做正确的事情,例如,不删除 cmets 和不插入行指令,这样的个人不喜欢只会妨碍解决问题并且不专业。
    • @Jim,你说的有道理,个人不喜欢,如果影响结果,就是不好。但是,我可以争辩说,忽略编码标准是更糟糕的主意,但是您可以帮助 OP 解决这个问题:-) 无论如何,您的答案当然是完全有效的。
    • “我假设宏定义在头文件中”——嗯,您自己的示例在本地包含它们。无论如何,我的解决方案(在其最新版本中)允许以任何一种方式进行。 “无论如何,你必须在运行脚本后编辑文件”——不,你不需要。脚本可以从 Makefile 中调用,整个过程会自动运行。
    【解决方案3】:

    我决定添加另一个答案,因为它完全不同。

    您是否考虑过使用 const 变量和 inline 函数作为替代方案,而不是使用技巧将宏扩展到项目源存储库?

    基本上,这些是宏在您的项目中“不受欢迎”的原因。

    您必须记住 inline 只是一个“建议”(即:该函数实际上可能没有内联),const 将使用内存而不是常量文字(嗯,取决于编译器,好的编译器会优化),但这会做两件事:

    1. 保持您的代码符合项目编码标准(这始终是一件好事,至少在政治上如果不一定在技术上)
    2. 不需要代表您的其他隐藏脚本或操作来保持代码的可重用性和可维护性(我假设您想使用宏来避免重复代码,对吧?)

    因此,请记住这一点,作为一种选择。

    【讨论】:

    • +1 用于从我们在另一个线程中的交换中提取肉并将其转化为可靠的答案......做得好......并超越了上述问题,进入元问题... X-Y 问题的一个示例 (google.com/search?q=X-Y+problem)。
    • 其中很多不是常量,也不是很容易在 c 函数中实现的东西。除了宏(使用#define M(f,a) ... ret_val = func args ... "M(func_x,(argy, argz))" 扩展为 "func_x (argy,argz)" 技巧
    【解决方案4】:

    作为您的问题的一种可能解决方案:“编写一个宏,然后通过替换为等效函数将其丢弃”,您可以使用原型函数式宏。它们有一些限制,必须小心使用。但它们的工作方式几乎与函数相同。

    #define xxPseudoPrototype(RETTYPE, MACRODATA, ARGS) typedef struct { RETTYPE xxmacro__ret__; ARGS } MACRODATA
    
    xxPseudoPrototype(float, xxSUM_data, int x; float y; );
    xxSUM_data xxsum;
    #define SUM_intfloat(X, Y) ( xxsum = (xxSUM_data){ .x = (X), .y = (Y) }, \
        xxsum.xxmacro__ret__ = xxsum.x + xxsum.y, \
        xxsum.xxmacro__ret__)
    

    我已经在这里解释了细节(主要是第 4 节,用于类似函数的宏):

    Macros faking functions

    • 第一行定义了一个宏,可用于为宏声明 pseudoprototypes
    • 第二行使用提供这种伪原型的宏。它定义了所需类型的“正式”参数。它依次包含宏所需的“返回”类型,将保存宏参数的结构的名称,最后是宏的参数(带有类型!)。我更喜欢称它们为伪参数
    • 第 3 行是强制性声明,它使 伪参数“真实”。它声明了一个结构,它是有必要编写的。它定义了一个包含 pseudoparameters 的“真实”版本的结构。
    • 最后,宏本身被定义为表达式的链表,用逗号分隔。第一个操作数用于将宏的参数“加载”到“真实”类型参数中。最后一个操作数是“返回值”,它也具有所需的类型。

    观察编译器对类型进行正确且透明的诊断。
    (但是,如链接中所述,有必要注意这些构造)。

    现在,如果您可以将宏的所有句子收集为以逗号分隔的函数调用链,那么您可以根据需要获得类似函数的宏。

    此外,您可以轻松地将其转换为真正的函数,因为参数列表已经定义。类型检查已经完成,所以一切都会正常工作。 在上面的示例中,您必须将所有行(第一行除外)替换为其他行:

    #define xxPseudoPrototype(RETTYPE, MACRODATA, ARGS) typedef struct { RETTYPE xxmacro__ret__; ARGS } MACRODATA
    
    float SUM_intfloat(int x, float y) {                       /* (1) */
       xxPseudoPrototype(float, xxSUM_data, int x; float y; ); /* (2) */
       xxSUM_data xxsum;                                       /* (2) */
       return                                                  /* (3) */
         ( xxsum = (xxSUM_data){ .x = x, .y = y },             /* (4) (5) (6) */
             xxsum.xxmacro__ret__ = xxsum.x + xxsum.y,         /* (5) (6) */
             xxsum.xxmacro__ret__)                             /* (6) */ 
        ;                                                      /* (7) */
     }                                                         /* (8) */
    

    替换将遵循系统程序:

    (1) 宏头变成函数头。分号 (;) 替换为逗号 (,)。
    (2) 声明行移到函数体内。
    (3) 增加了“回”字。
    (4) 宏参数 X, Y, 替换为函数参数 x, y。
    (5) 删除所有结尾的“\”。
    (6) 所有中间计算和函数调用都保持不变。
    (7) 添加分号。
    (8) 关闭函数体。

    问题:虽然这种方法可以解决您的需求,但请注意该函数复制了它的参数列表。这不好:必须删除伪原型和重复项:

    float SUM_intfloat(int x, float y) { 
       return  
         ( x + y )
        ;  
     }      
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-18
      相关资源
      最近更新 更多