检测printf()参数中的函数调用

时间:2011-12-06 15:18:55

标签: c++ logging printf

很长一段时间以来,我们的日志记录系统就像这样:

#define LOG( LEVEL, FORMAT, ... ) my_log_function( LEVEL, __FUNCTION__, \
                                         __LINE__, FORMAT, __VA_ARGS__ )

my_log_function将检查当前的日志记录级别,如果可以接受,将对传递的信息进行一些漂亮的打印(文件/行记录,时间等等)。

现在问题是,这个宏定义有两个巨大的缺点:

1 /使用时,在该宏中传递的参数是评估的,这意味着当您的代码被LOG()调用拥挤时会有很多性能命中。

参见此处的示例:

LOG( INFO, "The parameters: %s %d %d\n", heavyMethod().name(),     
        heavyMethod2().id(), work_done_in_this_function());

即使取消激活“INFO”记录级别,也会在进入该功能之前评估所有参数 如果您记录函数调用,您会看到这种情况发生:

  • 致电heavyMethod()
  • 致电name()
  • 致电heavyMethod2()
  • 致电id()
  • 致电work_done_in_this_function()
  • (最后)致电my_log_function()

当你有1000次LOG()调用时,这非常糟糕。

解决方案很简单:取出my_log_function检查级别的代码并修改LOG()定义,如下所示:

#define LOG( LVL, FMT, ... ) do{ if( level_enabled(LVL) )        \ 
                                   {                             \
                                     my_log_function( LVL, ...); \
                                   }                             \
                               }while(0)

这可以确保当日志级别不足时,不会评估参数(因为它们位于括号块中)。

2 /正如您在我的示例中看到的那样,调用的最后一个函数正在执行某些工作,如果未调用LOG()函数则无法完成。
这在我们的代码中发生了很多(我知道,这很糟糕,人们已经失去了手指)。

通过我在 1 /点上进行的增强,我们现在必须检查每个LOG()调用,看看是否已经完成了一些不再完成的工作,现在我们已经中和了电话。

这是你们输入的地方:你知道一个简单的方法来检查一个函数的参数是否实际修改了某些内容吗?

该项目采用C ++,大多数“不修改任何东西”的函数都标记为const。

请注意,这包括一些棘手的事情:LOG( INFO, "Number of items: %d\n", _item_number++);,其中_item_number是对象的类成员(因此,如果未激活INFO级别,则不会增加:-()。

TL; DR :永远不要在printf()中工作。总是事先做好:

// BAD
printf("Number: %d\n",++i);

// GOOD
i++;
printf("Number: %d\n", i);

1 个答案:

答案 0 :(得分:2)

基本上,您正在尝试检查纯函数(即没有副作用的函数)。检查这个的一个简单方法是检查它是否没有写入全局内存。

GCC支持几种强制执行此操作的方法。首先,有const属性,当应用于函数时,会导致它无法从全局内存(只有自己的堆栈)读取或写入。问题是,这可能对你想要的东西有点限制。

还有pure属性,它基本上是相同的,但允许从全局内存访问,但不允许访问,这也应该强制执行你想要的。

通过方法声明放置__attribute__(pure)(或__attribute__(const))来应用其中的每一个。遗憾的是,我认为有一种方法可以强制执行每次调用,尽管可能有一种使用函数指针的方法。如果找到,我会更新。

参考:http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

编辑:如列出here,可以将它应用于函数指针,虽然我认为没有办法在没有将函数指针嵌入到宏中的每个方法调用中的情况下执行此操作需要知道每个函数的参数布局,或者将所有函数声明为纯函数。

EDIT2:是的,这也不会在参数列表中捕捉i++之类的内容。那些必须使用我认为的一些正则表达式魔法来完成。