【问题标题】:Detecting null parameter in preprocessor macro检测预处理器宏中的空参数
【发布时间】:2011-10-31 12:35:28
【问题描述】:

我在原版C中有以下宏函数:

#define GLOG(format_string, ...) { \
  const char *file = strrchr(__FILE__, '/'); \
  char format[256] = "%s:%s!%d\t"; \
  strncat(format, format_string, 248); \
  strcat(format, "\n"); \
  printf(format, __FUNCTION__, file ? file : __FILE__, __LINE__, ##__VA_ARGS__); \
}

它让我打印一条包含当前函数、文件和行号的调试消息,例如

GLOG("count=%d", count);

可能会打印

do_count:counter.c!123  count=456
  1. 如果调用者省略了 format_string,如何修改函数以打印所有局部变量?例如

    GLOG();
    

    可能会打印

    do_count:counter.c!123  count=456, message="Hello world", array=[7, 8] structure={ptr=0xACE0FBA5E, coord={x=9, y=0}}
    
  2. 如果这不可能,我该如何修改它以仅打印当前函数、文件和行号?例如

    do_count:counter.c!123
    

    照原样,这会返回一个错误:

    错误:',' 标记之前的预期表达式

    strncat 行很简单

    strncat(format, , 248);
    

【问题讨论】:

    标签: c macros c-preprocessor


    【解决方案1】:

    首先,进程本身在运行时检查所有局部变量似乎是不可能的,因为 C 没有任何反射手段。

    其次,如果你像这样编写日志记录宏,你会更好:

    #include <stdio.h>
    
    #define STRINGIFY(x) #x
    #define TOSTRING(x) STRINGIFY(x)
    
    #define GLOGF(fmt, ...) \
      printf("%s:%s " fmt "\n", __func__, __FILE__ "!" TOSTRING(__LINE__), ##__VA_ARGS__)
    
    int main (void) {
      /* main:test.c!xx count=5 */
      GLOGF("count=%d", 5);
      /* main:test.c!xx */
      GLOGF();
      return 0;
    }
    

    它更简单,并且不会产生任何额外的运行时开销,因为字符串是在编译时连接的。

    另外请注意,我使用了__func__ 而不是__FUNCTION__,因为后者是非标准的。

    【讨论】:

    • 感谢您的建议,但它并不能完全回答问题,因为 GLOGF 和 GLOG 是不同的函数,用户必须记住在这两种情况下使用哪个。
    • @Gnubie:检查更新。如果这是您需要的,您仍然可以致电GLOGF() 并获取main:test.c!xx。
    【解决方案2】:

    我在this 答案中找到了this 链接。它可能会帮助您解决问题的第一部分。

    第二个,如何获取所有局部变量,即使不是不可能,也更难。原因是代码在编译时实际上没有变量,它只是在内存区域(堆栈)中具有偏移量。您的编译器可能具有可用于检查堆栈的内部函数,但是那么你只有可能的值而不是变量的名称。我看到的唯一解决方案是使用特殊的预处理器宏来声明局部变量,然后使用一个结构列表来表示它们以进行自省,这将是很多运行时和内存开销。

    【讨论】:

    • 谢谢,我怀疑在 C 中可能无法进行反射。
    【解决方案3】:

    正如其他人在这里提到的,C 没有反射特性,因此您将无法在宏调用中捕获局部变量。话虽如此,如果您希望根据宏调用是否有任何参数(即您的“非空”和“空”参数)有条件地使用宏发生某些事情,那么您可以执行类似的操作以下:

    #include <string.h>
    
    #define NULL_IDENT ""
    #define IDENT(ident_name) #ident_name
    #define MACRO(ident_name) \
        if (strcmp(NULL_IDENT, IDENT(ident_name)) == 0) { \
           /* add code for a null argument passed to the macro */ } \
        else { \
           /* add code for a non-null argument passed to the macro */ } 
    

    【讨论】:

      【解决方案4】:

      根据 Blagovest Buyukliev 的回答,我为第 2 部分提出了以下解决方案:

      #define GLOG(fmt, ...) do { const char *fn = strrchr(__FILE__, '/');           \
        printf("%s:%s!%d\t"fmt"\n",__func__,fn?++fn:__FILE__,__LINE__,##__VA_ARGS__);\
       } while(0)
      

      如果参数被省略,则使用预处理器的字符串连接来简单地连接一个空字符串。

      此外,我添加了 do {...} while(0) 以吞下结尾的分号,以便以下 if...else 起作用:

      if (...)
       GLOG();
      else     
       /* do something else */
      

      (来自http://gcc.gnu.org/onlinedocs/cpp/Swallowing-the-Semicolon.html 的想法)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-27
        • 1970-01-01
        • 2011-05-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多