【问题标题】:Implementing formatted print with the possibility to do nothing when it gets no arguments实现格式化打印,当它没有参数时可以不做任何事情
【发布时间】:2019-04-17 06:30:52
【问题描述】:

我想实现一个名为PRINT 的宏,它获取零个或多个参数,并执行以下操作:

  1. 如果它得到零参数 - 什么也不做。
  2. 如果它得到一个或多个参数 - 像 printf 一样操作。

正如您在下面的代码中看到的那样,我成功地实现了它,但仅以在我们得到零参数的情况下使用空字符串调用 printf 为代价。

有没有一种方法可以在不调用 printf 的情况下处理零参数的情况(当您只想什么都不做时打印一些东西效率不高)?

#include <stdio.h>

#define PRINT(...) printf("" __VA_ARGS__);

int main(){
    PRINT();
    PRINT("print\n");
    PRINT("print number: %d\n", 7);
    return 0;
}   

输出:

print
print number: 7  

【问题讨论】:

  • 空的PRINT()从何而来?从另一个宏? (任何人类程序员都可能直接删除它。)
  • Jens Gustedt answered 一个关于检测空参数列表和showed how 的问题。这是相当复杂的。

标签: c macros printf variadic-functions


【解决方案1】:

gcc 和 clang 都将完全消除对 printf 的调用,如果它被传递一个空格式字符串。这种优化可能相当普遍。

在 gcc.godbolt.org 上查看 herehere 进行反汇编。

总之,不用担心。

【讨论】:

    【解决方案2】:

    如果您确实想将空参数列表的检查外包给宏,那么可能是这样的:

    #define HAS_ARGS(...) (sizeof( (char[]){#__VA_ARGS__} ) > 1)
    
    #define PRINT(...) (HAS_ARGS(__VA_ARGS__) ? printf("" __VA_ARGS__) : (void)0)
    

    这依赖于在空字符串 = null 终止符的情况下获得大小为 1 的复合文字。示例:

    #include <stdio.h>
    
    #define HAS_ARGS(...) (sizeof( (char[]){#__VA_ARGS__} ) > 1)
    
    #define PRINT(...) (HAS_ARGS(__VA_ARGS__) ? printf("" __VA_ARGS__) : (void)0)
    
    int main (void)
    {
      int i = 5;
      PRINT("%d\n", i);
      PRINT();
    }
    

    【讨论】:

      【解决方案3】:

      解决办法是:

      #define Argument1(a,...)    Argument2(a
      #define Argument2(a, b,...) b
      #define TestEmpty()         ,
      #define PRINT(...)          Argument1(TestEmpty __VA_ARGS__ (),), printf(__VA_ARGS__);,)
      

      对于此源文本:

      Test 0: PRINT()
      Test 1: PRINT("Hello, world.\n")
      Test 2: PRINT("Result is %d.\n", result)
      Test 3: PRINT("%d = %g.\n", 3, 3.)
      

      预处理的结果是:

      Test 0:
      Test 1: printf("Hello, world.\n");
      Test 2: printf("Result is %d.\n", result);
      Test 3: printf("%d = %g.\n", 3, 3.);
      

      这要求PRINT 的参数不能以带括号的项目开头,这对于这个特定问题似乎是可以接受的。此处的代码不是在可能存在括号时检测空参数的通用解决方案。

      解释:

      • 在准备替换 PRINT 时,替换了 __VA_ARGS__TestEmpty 此时不会被替换,因为它后面没有括号。
      • 然后PRINT被替换,结果被重新扫描。
      • Argument1 被识别为替换。为此,会处理其参数。
      • 此时,如果__VA_ARGS__ 为空,我们就有了标记TestEmpty (),它们被, 替换。否则,TestEmpty &lt;some tokens&gt; () 仍然存在。请注意,如果存在TestEmpty (),则它扩展为,Argument1 的第一个参数。它不是参数分隔符,因为 Argument1 的参数已被识别。
      • 然后Argument1 被替换,生成Argument2(,(如果__VA_ARGS 为空)或Argument2(TestEmpty,可能后面跟着额外的令牌(否则)。
      • 根据__VA_ARGS__ 是否为空,结果为Argument2(,, printf();,)Argument2(TestEmpty &lt;some tokens&gt;, printf(&lt;some tokens&gt;);,)
      • 现在重新扫描标记以进行进一步替换,因此 , 现在将被识别为参数分隔符。
      • 最后,Argument2 被替换为空令牌列表或printf(&lt;some tokens&gt;);

      【讨论】:

      • 预处理器的出色使用。但是测试 4 呢? char *str = "This is a string.\n"; PRINT(str); 还是测试 5? char *str = ""; PRINT(str); 我也非常关心可维护性,不愿意在一些大型项目的荒野中发布这些宏。
      • @AndrewHenle:您提议的测试 4 和 5 根据需要生成printf(str);。只要第一个参数不以括号开头,我认为宏就可以工作。如果它们以任何非空括号开头,则应生成错误,因此将捕获问题。 printf 参数很少以括号开头。我怀疑最有可能的情况是PRINT(SomeMacroToPrepareFormatString(stuff),…),其中宏扩展为带括号的序列(出于括号宏替换文本的常见习惯,在这种情况下不是必需的......
      • @AndrewHenle:……除非替换文本包含逗号运算符,该运算符需要用括号括起来以防止将其解释为参数分隔符)。对于大型项目,我不建议这样做;我只是在回答 Stack Overflow 问题,因为知识很有价值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-02-17
      • 2020-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-16
      • 2023-03-12
      相关资源
      最近更新 更多