自定义支持__attribute __((格式))

时间:2013-05-02 09:07:29

标签: c++ c gcc printf clang

GCC和Clang都支持对printf等变量参数函数进行编译时检查。这些编译器接受如下语法:

extern void dprintf(int dlevel, const char *format, ...)
  __attribute__((format(printf, 2, 3)));  /* 2=format 3=params */

在OSX上,Cocoa框架也对NSString使用了这个扩展:

#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))

在我们公司,我们有一个自定义C ++框架,其中包含许多类BaseString,这些类都来自BaseObject。在BaseString中有一些类似于sprintf的变量参数方法,但有一些扩展。例如,"%S"期望参数类型为BaseString*"%@"期望BaseObject*参数。

我想对项目中的参数执行编译时检查,但由于扩展,__attribute__((format(printf)))会给出很多误报警告。

有没有办法为两个编译器之一定制__attribute__((format))的支持?如果这需要编译器源的补丁,它是否可以在合理的时间内完成?或者,还有其他 lint 这样的工具可以执行检查吗?

4 个答案:

答案 0 :(得分:5)

使用最新版本的GCC(我推荐使用4.7或更新版本,但您可以尝试使用GCC 4.6),您可以通过GCC插件(使用PLUGIN_ATTRIBUTES挂钩)添加自己的变量和函数属性,或者MELT扩展名。 MELT是一种扩展GCC的域特定语言(作为[meta-]插件实现)。

如果使用插件(例如MELT),则无需重新编译GCC的源代码。但是你需要一个支持插件的GCC(请查看gcc -v)。

某些Linux发行版未在其gcc中启用插件 - 请向您的发行商投诉;其他人提供了GCC插件开发的软件包,例如: Debian或Ubuntu gcc-4.7-plugin-dev

答案 1 :(得分:2)

这是可行的,但这当然不容易;部分问题是BaseStringBaseObject是用户定义的类型,因此您需要动态定义格式说明符。幸运的是,gcc至少支持这一点,但仍需要修补编译器。

魔术位于handle_format_attribute中的gcc/c-family/c-format.c函数中,该函数调用引用用户定义类型的格式说明符的初始化函数。基于您的支持的一个很好的示例是gcc_gfc格式类型,因为它为%L定义了格式说明符locus *

/* This will require a "locus" at runtime.  */
{ "L",   0, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "R", NULL },

显然,您希望将format_char_info数组基于print_char_table,因为它定义了标准的printf说明符;相比之下,gcc_gfc大幅缩减。

添加gcc_gfc的补丁是http://gcc.gnu.org/ml/fortran/2005-07/msg00018.html;从那个补丁中你应该如何以及在何处需要添加它。

答案 2 :(得分:2)

在问了这个问题一年半后,我提出了一种完全不同的方法来解决实际问题:有没有办法静态检查自定义可变参数格式化语句的类型?

为了完整性,因为它可以帮助其他人,这是我最终实现的解决方案。它比原来的问题有两个优点:

  • 相对简单:在不到一天的时间内实施;
  • 独立于编译器:可以在任何平台(Windows,Android,OSX等)上检查C ++代码。

Perl脚本解析源代码,查找格式化字符串并解码其中的百分比修饰符。然后它通过调用模板标识函数CheckFormat<>来包装所有参数。例如:

str->appendFormat("%hhu items (%.2f %%) from %S processed", 
    nbItems, 
    nbItems * 100. / totalItems, 
    subject);

变为:

str->appendFormat("%hhu items (%.2f %%) from %S processed", 
    CheckFormat<CFL::u, CFM::hh>(nbItems  ), 
    CheckFormat<CFL::f, CFM::_>(nbItems * 100. / totalItems  ), 
    CheckFormat<CFL::S, CFM::_, const BaseString*>(subject  ));

枚举CFLCFM和模板函数CheckFormat必须在这样的公共头文件中定义(这是一个提取,大约有24个重载)。

enum class CFL
{
    c, d, i=d, star=i, u, o=u, x=u, X=u, f, F=f, e=f, E=f, g=f, G=f, p, s, S, P=S, at
};
enum class CFM
{
    hh, h, l, z, ll, L=ll, _
};
template<CFL letter, CFM modifier, typename T> inline T CheckFormat(T value) { CFL test= value; (void)test; return value; }
template<> inline const BaseString* CheckFormat<CFL::S, CFM::_, const BaseString*>(const BaseString* value) { return value; }
template<> inline const BaseObject* CheckFormat<CFL::at, CFM::_, const BaseObject*>(const BaseObject* value) { return value; }
template<> inline const char* CheckFormat<CFL::s, CFM::_, const char*>(const char* value) { return value; }
template<> inline const void* CheckFormat<CFL::p, CFM::_, const void*>(const void* value) { return value; }
template<> inline char CheckFormat<CFL::c, CFM::_, char>(char value) { return value; }
template<> inline double CheckFormat<CFL::f, CFM::_, double>(double value) { return value; }
template<> inline float CheckFormat<CFL::f, CFM::_, float>(float value) { return value; }
template<> inline int CheckFormat<CFL::d, CFM::_, int>(int value) { return value; }

...

在出现编译错误后,很容易恢复原始表单,并将正则表达式CheckFormat<[^<]*>\((.*?) \)替换为其捕获。

答案 3 :(得分:1)

使用c ++ 11,可以通过用__attribute__ ((format))constexpr和可变参数包的巧妙组合替换decltype来解决此问题。将格式字符串传递给constexpr函数,该函数在编译时提取所有%说明符,并验证第n个说明符是否匹配(n + 1)的decltype' st论点。