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 这样的工具可以执行检查吗?
答案 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)
这是可行的,但这当然不容易;部分问题是BaseString
和BaseObject
是用户定义的类型,因此您需要动态定义格式说明符。幸运的是,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)
在问了这个问题一年半后,我提出了一种完全不同的方法来解决实际问题:有没有办法静态检查自定义可变参数格式化语句的类型?
为了完整性,因为它可以帮助其他人,这是我最终实现的解决方案。它比原来的问题有两个优点:
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 ));
枚举CFL
,CFM
和模板函数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论点。