用“函数属性”来避免C中格式化字符串时可能存在的错误
为了说明这个问题,先来看下这个简单的C程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <stdio.h> #include <stdarg.h> void writeLogInfo(const char *sFormat, ...){ char sOutBuffer[4196]; va_list lvalist; va_start(lvalist, sFormat); vsnprintf(sOutBuffer, sizeof(sOutBuffer)-2, sFormat, lvalist); va_end(lvalist); printf("log: %s\n", sOutBuffer); } int main(){ writeLogInfo("int=%s", 123); return 0; } |
这程序用gcc编译,即使是用 -Wall 打开所有的警告,也是不会有任何报错的。
但是执行结果是什么呢?由于 writeLogInfo 的是一个参数里指定的是 %s ,而第二个参数确是整型数字 123。所以程序义无反顾地出现了“段错误”而崩溃掉。这种问题在项目代码超过万行以后,要debug起来,也是会浪费很多时间的。
有的人会发现,如果把main里的writeLogInfo直接换成printf,那么在编译的时候,gcc会报一个警告:“警告:格式‘%s’需要类型‘char *’,但实参 2 的类型为‘int’”(Gcc4.x默认就会报,Gcc3.x要加 -Wall 选项才报),如果我们自己的定义的writeLogInfo函数也能有这个警告,那么这种bug将在编译的时候就可以完美解决了。
那么具体怎么实现呢?先来看下面这段代码,功能是和上面的完全一样的,连错误都一样,呵呵:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h> #include <stdarg.h> #ifndef __GNUC__ # define __attribute__(x) /*NOTHING*/ #endif void writeLogInfo(const char *sFormat, ...){ char sOutBuffer[4196]; va_list lvalist; va_start(lvalist, sFormat); vsnprintf(sOutBuffer, sizeof(sOutBuffer)-2, sFormat, lvalist); va_end(lvalist); printf("log: %s\n", sOutBuffer); } void writeLogInfo(const char *sFormat, ...) __attribute__((format(printf,1,2))); int main(){ writeLogInfo("int=%s", 123); return 0; } |
当你尝试用gcc编译这个文件的时候,你就可以看到警告了,哈哈。
可以看到,这里的关键就是“__attribute__((format(printf,1,2)))” ,这句话的作用就是告诉编译器,前面这个函数呢,参数类型是类似printf的,格式化字符串在参数的第1个位置,扩展参数从第2个位置开始,然后编译器就明白了~
然后,上面的4~6行呢,是为了兼容非Gcc的编译器而加的,这样其他的编译器就会直接无视整个 __attribute__ 了,这样至少不会报错。
其实,这个检查格式化字串的功能(format),只是“函数属性”的一个而已,另外还有许多有用又有意思的属性,比如函数的别名啊(alias),是否已经过时啊(deprecated),等等~要了解这些用法的话,建议去看看官方文档。
levon 在 2010年01月04日 17:41 说:【 】
先mark一下,吃飯回來仔細讀讀
Iven 在 2010年01月04日 17:49 说:【 】
……以我目前的水平还用不到……
xiooli 在 2010年01月04日 18:25 说:【 】
看见c就头晕。
huntxu 在 2010年01月04日 18:41 说:【 】
学习了~
TualatriX 在 2010年01月04日 20:02 说:【 】
骨头很少写C语言的文章啊,一写就不得了。
boy89 在 2010年01月05日 13:02 说:【 】
哥,你这里有搞嵌入式的高人吗,去拜膜~~~~
bones7456 在 2010年01月05日 21:56 说:【 】
呵呵,什么叫“我这里”啊?
boy89 在 2010年01月06日 10:10 说:【 】
嘿嘿 比如说你的好友里面有弄嵌入式的牛人,给我说个他的博客地址,我去学习经验去~~~~
我在你这就学到很多知识啊~~~~有个地方我一直想问,就是怎样搭个源啊,你那源上的资源,是不是得从官方源上同步啊~~嘿嘿,问的不专业,哥也讲讲吧~~~
bones7456 在 2010年01月06日 12:10 说:【 】
恩,从官方同步好数据,然后架个nginx或者apache之类的,就可以了。具体我这里都写过的,可以在这里搜索看看。
Dig 在 2010年01月06日 11:46 说:【 】
gcc 有很多小窍门啊,attribute, throw 还有一些奇妙的宏
Dig 在 2010年01月06日 12:01 说:【 】
哈哈,现在成了挂名用户。
它提醒说
“WordPress 2.9.1 版本可用!请通知您的站点管理员。”
bones7456 在 2010年01月06日 12:08 说:【 】
呵呵,之前在等中文版呢,现在升级好了。
Dig 在 2010年01月06日 12:13 说:【 】
Warning: file_get_contents(http:) [function.file-get-contents]: failed to open stream: No such file or directory in /home8/gnometwe/public_html/li2z/wp-includes/class-feed.php on line 97
RSS错误:file_get_contents could not read the file
Dig 在 2010年01月06日 12:51 说:【 】
控制板 里面的 WP开发博客 和 其它新闻 出现这样的错误提示
dq 在 2010年02月06日 15:29 说:【 】
好文,有用
如果writeLogInfo是一个class method,则改成 attribute__((format(printf,2,3))) 因为第一个参数是隐藏的”this”指针
bones7456 在 2010年02月06日 16:36 说:【 】
对,那是C++的事情了,呵呵。