I am LAZY bones? AN ancient AND boring SITE

用“函数属性”来避免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),等等~要了解这些用法的话,建议去看看官方文档

最后修改时间: 2010年01月04日 17:32

本文章发表于: 2010年01月04日 17:32 | 所属分类:编程相关. | 您可以在此订阅本文章的所有评论. | 您也可以发表评论, 或从您的网站trackback.

16 个评论 关于: “用“函数属性”来避免C中格式化字符串时可能存在的错误”

  1. levon 在 2010年01月04日 17:41 说:回复

    先mark一下,吃飯回來仔細讀讀

  2. Iven 在 2010年01月04日 17:49 说:回复

    ……以我目前的水平还用不到……

  3. xiooli 在 2010年01月04日 18:25 说:回复

    看见c就头晕。

  4. huntxu 在 2010年01月04日 18:41 说:回复

    学习了~

  5. TualatriX 在 2010年01月04日 20:02 说:回复

    骨头很少写C语言的文章啊,一写就不得了。

  6. 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之类的,就可以了。具体我这里都写过的,可以在这里搜索看看。

  7. Dig 在 2010年01月06日 11:46 说:回复

    gcc 有很多小窍门啊,attribute, throw 还有一些奇妙的宏

  8. Dig 在 2010年01月06日 12:01 说:回复

    哈哈,现在成了挂名用户。
    它提醒说
    “WordPress 2.9.1 版本可用!请通知您的站点管理员。”

    • bones7456 在 2010年01月06日 12:08 说:回复

      呵呵,之前在等中文版呢,现在升级好了。

  9. 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开发博客 和 其它新闻 出现这样的错误提示

  10. dq 在 2010年02月06日 15:29 说:回复

    好文,有用
    如果writeLogInfo是一个class method,则改成 attribute__((format(printf,2,3))) 因为第一个参数是隐藏的”this”指针

    • bones7456 在 2010年02月06日 16:36 说:回复

      对,那是C++的事情了,呵呵。

发表评论