2012年6月7日星期四

Gcc编译可变参数函数之__attribute__属性

    这篇文章又是为了纪念我碰到的无数坑中的一个。话说在其他部门做项目的时候,还是全新的代码,某天就碰到一个程序崩溃问题,通过代码走查和屏蔽代码的方法,发现是程序里面的log函数造成的,log函数有可变参数,由于在写参数的时候没注意参数匹配,会出现类型不匹配或者数目不匹配的情况,然后makefile又没有编译warning,这些可好,编译出来的都正常,结果一跑就异常。还好现在的代码不多,如果是几万,几十万行的代码,找这样一个错误非要找死人啊。我一直不太理解,为什么代码编译时都不编译warning呢,要知道,经常可以在看warning的过程中发现代码的问题啊。
    话说回来,如果仅仅是在gcc编译的时候加上-Wall选项,编译时是检查不出来可变参数不匹配的问题的。我也是google了好久才发现gcc一个特性__attribute__,可以用来解决此种问题。
    首先:

__attribute__语法格式为
__attribute__ ((attribute-list))
其位置约束为:
放于声明的尾部“;”之前。attribute-list可以有很多种,这里主要说format这个属性。
 
    具体的含义如下:

__attribute__ format
该__attribute__属性可以给被声明的函数加上类似printf 或者scanf 的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。

format 的语法格式为
format (archetype, string-index, first-to-check)
format 属性告诉编译器,按照printf, scanf, strftime 或strfmon 的参数表格式规则对该函数的参数进行检查。“archetype”指定是哪种风格;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。

具体使用格式如下:
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中参数m 与n 的含义为:
m:第几个参数为格式化字符串(format string)
n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的呢。


m和n的含义一不小心很容易搞错,我就是在这错了很多次,编译一直通不过。
比如说我自己写的一个函数:extern void WriteLog(const char *LogStr,...);
在使用时就是这样:WriteLog("info: %s %d",string info,int num);
那么传入函数的格式化字符串就是:"info: %s %d",即m为1。我们要检查是否匹配的第一个参数就是string info,这个参数在整个函数的参数集合里面位于第二位,所以n就是2。因此就要写成
extern void WriteLog(const char *LogStr,...) __attribute__((format(printf,1,2)));
如果这个函数是一个类的成员函数,由于成员函数默认带this指针,所以m和n的值就要加1,写法就变成:
void WriteLog(const char *LogStr,...) __attribute__((format(printf,2,3)));
由于是类的成员函数,所以上面的写法放在h文件里面就可以了。

    如果m和n的值写错,gcc会提示你n不能小于m啊,或者m的值不是对应的格式化字符串等等。把函数写好后,再加上-Wall选项,这个就会提示warning了。不过我现在还不知道可以让出现这种warning的时候把它当error来处理,虽然有选项可以让所有的warning视为error,但是有些warning是允许存在的,怎么区分这类warning呢?这个留待以后吧。

    另:gcc __attribute__属性的文档放到google drive里面了。嗯,我还真是个google控,但是google+ 界面太烂了。

没有评论:

发表评论