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

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可以执行检查?

使用最新版本的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

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

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

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

显然,虽然您希望将print_char_table数组基于print_char_table ,因为它定义了标准的printf说明符; gcc_gfc大幅减少。

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

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

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

  • 相对简单:在不到一天的时间内实施;
  • 编译器独立:可以在任何平台(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(nbItems ), CheckFormat(nbItems * 100. / totalItems ), CheckFormat(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 inline T CheckFormat(T value) { CFL test= value; (void)test; return value; } template<> inline const BaseString* CheckFormat(const BaseString* value) { return value; } template<> inline const BaseObject* CheckFormat(const BaseObject* value) { return value; } template<> inline const char* CheckFormat(const char* value) { return value; } template<> inline const void* CheckFormat(const void* value) { return value; } template<> inline char CheckFormat(char value) { return value; } template<> inline double CheckFormat(double value) { return value; } template<> inline float CheckFormat(float value) { return value; } template<> inline int CheckFormat(int value) { return value; } ... 

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

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