程序员是追求完美的一族,即使是一般的程序员大多也都不想看到自己的程序中有甚至那么一点点的瑕疵。遇到任意一条编译器警告都坚决不放过。有人会说:我们可以使用比编译器更加严格的静态代码检查工具,如splint。这个建议也很不错。不过lint工具使用起来较繁琐,有时候还需要记住一些特定符号并插入到你自己的代码中才行,门槛较高,这也让很多人止步于此。那么我们就从此放弃么?不,如今的编译器做得都很好,它可以帮助我们的找到绝大多数可能出现问题的代码,前提是你要学会控制编译器去找到这些问题代码,而熟悉编译器的警告选项恰恰是体现控制力的好方法。当你可以自如控制编译器警告输出的时候,你就算是'入道'了,同时你对语言的理解也更进一步了。
有人说:我就是用一个-Wall选项就可以了,一般选手可以这么做,而且他可以不知道-Wall会跟踪哪些类型的问题;但是高级选手是不会只使用- Wall的,他会把每条警告都研究的很透彻,会在Makefile中列出他想让编译器输出哪些类型的警告以替代-Wall,他会屏蔽掉那些对他的代码'毫无用处'的警告(很可能他使用了编译器对语言的扩展功能),他会有个和编译器交流的过程。
俗话说:'工欲善其事,必先利其器',一直在工作中使用GNU C编译器(以下简称GCC),这里对GCC的一些警告选项细致的分析,并列举几个简单的例子[注1]供分析参考。
1. -Wall集合警告选项
我们平时可能大多数情况只使用-Wall编译警告选项,实际上-Wall选项是一系列警告编译选项的集合。下面逐一分析这一集合中的各个选项:
[-Wchar-subscripts]
如果数组使用char类型变量做为下标值的话,则发出警告。因为在某些平台上char可能默认为signed char,一旦溢出,就可能导致某些意外的结果。
e.g.
/* test_signed_char.c */
#i nclude
int main () {
char c = 255; // 我们以为char是无符号的,其范围应该是[0,255]
int i = 0;
int a[256];
for (i = 0; i < 256; i++) {
a[i] = 1;
}
printf("%d\n", c); // 我们期待输出255
printf("%d\n", a[c]); // 我们期待输出1
printf("%d\n", a[255]);
return 0;
}
gcc -Wchar-subscripts test_signed_char.c
test_signed_char.c: In function `main':
test_signed_char.c:13: warning: array subscript has type `char'
其输出结果:
-1
-4197476
1
从输出结果来看Solaris 9/gcc 3.2上char默认实现类型为signed char;在Windows XP/gcc-3.4.2上也是一样。
Windows上的输出结果:
-1
16 (随机值)
1
[-Wcomment]
当'/*'出现在 '/* ... */'注释中,或者'\'出现在'// ...'注释结尾处时,使用-Wcomment会给出警告。不要小觑这些马虎代码,它很可能会影响程序的运行结果。如下面的例子:
e.g.
/*
* test_comment.c
* gcc -Wcomment test_comment.c
*/
#i nclude
int main() {
int a = 1;
int b = 2;
int c = 0; // ok just test\
c = a + b;
/*
* 这里我们期待c = 3
* /* 但实际上输出c = 0
*/
printf("the c is %d\n", c);
return 0;
}
gcc -Wcomment test_comment.c
test_comment.c:10:30: warning: multi-line comment
test_comment.c:15:12: warning: "/*" within comment
输出:
the c is 0
[-Wformat]
检查printf和scanf等格式化输入输出函数的格式字符串与参数类型的匹配情况,如果发现不匹配则发出警告。某些时候格式字符串与参数类型的不匹配会导致程序运行错误,所以这是个很有用的警告选项。
e.g.
/*
* test_format.c
*/
#i nclude
int main() {
long l = 1;
double d = 55.67;
printf("%d\n", l);
printf("%d\n", d);
return 0;
}
gcc -Wformat test_format.c
test_format.c: In function `main':
test_format.c:10: warning: int format, long int arg (arg 2)
test_format.c:11: warning: int format, double arg (arg 2)
输出:
1
1078711746
[-Wimplicit]
该警告选项实际上是-Wimplicit-int和-Wimplicit-function-declaration两个警告选项的集合。前者在声明函数却未指明函数返回类型时给出警告,后者则是在函数声明前调用该函数时给出警告。
e.g.
/*
* test_implicit.c
*/
#i nclude
add(int a, int b) { //函数没有声明返回类型
return a + b;
}
int test() {
int a = 0;
int b = 0;
int c = 0;
int d = 0;
c = add(a, b);
d = sub(a, b); //未声明sub的函数原型
return 0;
}
gcc -Wimplicit -c test_implicit.c
test_implicit.c:7: warning: return type defaults to `int'
test_implicit.c: In function `test':
test_implicit.c:18: warning: implicit declaration of function `sub'
[-Wmissing-braces]
当聚合类型或者数组变量的初始化表达式没有'充分'用括号{}括起时,给出警告。文字表述很难理解,举例说明则清晰些。看下面的例子:
e.g.
/*
* test_missing_braces.c
*/
struct point {
int x;
int y;
};
struct line {
struct point start;
struct point end;
};
typedef struct line line;
int main() {
int array1[2][2] = {11, 12, 13, 14};
int array2[2][2] = {{11, 12}, {13, 14}}; // ok
line l1 = {1, 1, 2, 2};
line l2 = {{2, 2}, {3, 3}}; // ok
return 0;
}
gcc -Wmissing-braces test_missing_braces.c
test_missing_braces.c: In function `main':
test_missing_braces.c:19: warning: missing braces around initializer
test_missing_braces.c:19: warning: (near initialization for `array1[0]')
test_missing_braces.c:21: warning: missing braces around initializer
test_missing_braces.c:21: warning: (near initialization for `l1.start')
[-Wparentheses]
这是一个很有用的警告选项,它能帮助你从那些看起来语法正确但却由于操作符优先级或者代码结构'障眼'而导致错误运行的代码中解脱出来。好长的一个长句,还是看例子理解吧!:)
e.g.
/*
* test_parentheses.c
* gcc -Wparentheses test_parentheses.c
*/
#i nclude
int main() {
int a = 1;
int b = 1;
int c = 1;
int d = 1;
if (a && b || c) { // 人们很难记住逻辑操作符的操作顺序,所以编译器建议加上()
;
}
if (a == 12)
if (b)
d = 9;
else
d = 10; //从代码的缩进上来看,这句仿佛是if (a == 12)的else分支
printf("the d is %d\n", d); //期待d = 10, 而结果却是1
return 0;
}
gcc -Wparentheses test_parentheses.c
test_parentheses.c: In function `main':
test_parentheses.c:13: warning: suggest parentheses around && within ||
test_parentheses.c:17: warning: suggest explicit braces to avoid ambiguous `else'
输出:
the d is 1
