程序员是追求完美的一族,即使是一般的程序员大多也都不想看到自己的程序中有甚至那么一点点的瑕疵。遇到任意一条编译器警告都坚决不放过。有人会说:我们可以使用比编译器更加严格的静态代码检查工具,如
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 type
`char’
其输出结果:
-1
-41Array7476
1
从输出结果来看Solaris Array/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:1Array: warning: missing braces around
initializer
test_missing_braces.c:1Array: 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 = Array;
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
[-Wsequence-point]
关于顺序点(sequence
point),在C标准中有解释,不过很晦涩。我们在平时编码中尽量避免写出与实现相关、受实现影响的代码便是了。而-Wsequence-point选项恰恰可以帮我们这个忙,它可以帮我们查出这样的代码来,并给出其警告。
e.g.
/*
*
test_sequence_point.c
* gcc -Wsequence-point
test_sequence_point.c
*/
#i nclude
int main() {
int i
= 12;
i = i--;
printf("the i is %d\n", i);
return 0;
}
gcc -Wsequence-point
test_sequence_point.c
test_sequence_point.c: In function
`main’:
test_sequence_point.c:10: warning: operation on `i’ may be
undefined
在两个平台上给出的编译警告都是一致的,但是输出结果却大相径庭。
Solaris输出:
the i is
11
Windows输出:
the i is 12
类似的像这种与顺序点相关的代码例子有:
i = i++;
a =
b[i++]
a[i++] =
i
等等...
[-Wswitch]
这个选项的功能浅显易懂,通过文字描述也可以清晰的说明。当以一个枚举类型(enum)作为switch语句的索引时但却没有处理default情况,或者没有处理所有枚举类型定义范围内的情况时,该选项会给处警告。
e.g.
/*
*
test_switch1.c
*/
enum week {
SUNDAY,
MONDAY,
TUESDAY /* only an example , we omitted the others */
};
int
test1() {
enum week w = SUNDAY;
switch(w)
{
case SUNDAY:
break; // without
default or the other case handlings
};
return
0;
}
int test2() { // Ok, won’t invoke even a warning
enum
week w = SUNDAY;
switch(w) {
case
SUNDAY:
break;
default:
break;
};
return
0;
}
int test3() { // Ok, won’t invoke even a warning
enum
week w = SUNDAY;
switch(w) {
case
SUNDAY:
break;
case MONDAY:
break;
case TUESDAY:
break;
};
return 0;
}
gcc
-Wswitch -c test_switch.c
test_switch.c: In function
`test1’:
test_switch.c:16: warning: enumeration value `MONDAY’ not handled in
switch
test_switch.c:16: warning: enumeration value `TUESDAY’ not handled in
switch
[-Wunused]
-Wunused是-Wunused-function、-Wunused-label、-Wunused-variable、-Wunused-value选项的集合,-Wunused-parameter需单独使用。
(1)
-Wunused-function用来警告存在一个未使用的static函数的定义或者存在一个只声明却未定义的static函数,参见下面例子中的func1和func2;
(2)
-Wunused-label用来警告存在一个使用了却未定义或者存在一个定义了却未使用的label,参加下面例子中的func3和func7;
(3)
-Wunused-variable用来警告存在一个定义了却未使用的局部变量或者非常量static变量;参见下面例子中func5和var1;
(4)
-Wunused-value用来警告一个显式计算表达式的结果未被使用;参见下面例子中func6
(5)
-Wunused-parameter用来警告一个函数的参数在函数的实现中并未被用到,参见下面例子中func4。
下面是一个综合的例子
e.g.
/*
*
test_unused.c
*/
static void func1(); //to prove function used but never
defined
static void func2(); //to prove function defined but not
used
static void func3(); //to prove label used but never defined
static
void func7(); //to prove label defined but never used
static void func4(int
a); //to prove parameter declared but not used
static void func5(); //to
prove local variable defined but not used
static void func6(); //to prove
value evaluated but not used
static int var1;
void test() {
func1();
func3();
func4(4);
func5();
func6();
}
static void func2() {
; // do
nothing
}
static void func3() {
goto over;
}
static void
func4(int a) {
; // do nothing
}
static void func5() {
int a = 0;
}
static void func6() {
int a =
0;
int b = 6;
a + b;
}
gcc -Wunused-parameter
-c test_unused.c //如果不是用-Wunused-parameter,则func4函数将不被警告。
test_unused.c: In
function `func3’:
test_unused.c:30: label `over’ used but not
defined
test_unused.c: In function `func7’:
test_unused.c:35: warning:
deprecated use of label at end of compound statement
test_unused.c:34:
warning: label `over’ defined but not used
test_unused.c: In function
`func4’:
test_unused.c:37: warning: unused parameter `a’
test_unused.c: In
function `func5’:
test_unused.c:42: warning: unused variable
`a’
test_unused.c: In function `func6’:
test_unused.c:48: warning:
statement with no effect
test_unused.c: At top level:
test_unused.c:6:
warning: `func1’ used but never defined
test_unused.c:25: warning: `func2’
defined but not used
test_unused.c:14: warning: `var1’ defined but not
used
[-Wuninitialized]
该警告选项用于检查一个局部自动变量在使用之前是否已经初始化了或者在一个longjmp调用可能修改
一个non-volatile automatic
variable时给出警告。目前编译器还不是那么smart,所以对有些可以正确按照程序员的意思运行的代码还是给出警告。而且该警告选项需要和’-
O’选项一起使用,否则你得不到任何uinitialized的警告。
e.g.
/*
*
test_uninitialized.c
*/
int test(int y) {
int x;
switch (y) {
case 1:
x =
11;
break;
case 2:
x = 22;
break;
case 3:
x = 33;
break;
}
return x;
}
gcc -Wuninitialized -O -c
test_uninitialized.c
test_uninitialized.c: In function
`test’:
test_uninitialized.c:6: warning: `x’ might be used uninitialized in
this
function
2、非-Wall集合警告选项
以下讨论的这些警告选项并不包含在-Wall中,需要程序员显式添加。
[-Wfloat-equal]
该项用来检查浮点值是否出现在相等比较的表达式中。
e.g.
/*
*
test_float_equal.c
*/
void test(int i) {
double d = 1.5;
if (d == i) {
;
}
}
gcc
-Wfloat-equal -c test_float_equal.c
test_float_equal.c: In function
`test’:
test_float_equal.c:8: warning: comparing floating point with == or !=
is
unsafe
[-Wshadow]
当局部变量遮蔽(shadow)了参数、全局变量或者是其他局部变量时,该警告选项会给我们以警告信息。
e.g.
/*
*
test_shadow.c
*/
int g;
void test(int i) {
short
i;
double g;
}
gcc -Wshadow -c test_shadow.c
test_shadow.c:
In function `test’:
test_shadow.c:Array: warning: declaration of `i’ shadows
a parameter
test_shadow.c:10: warning: declaration of `g’ shadows a global
declaration
test_shadow.c:6: warning: shadowed declaration is
here
[-Wbad-function-cast]
当函数(准确地说应该是函数返回类型)被转换为非匹配类型时,均产生警告。
e.g.
/*
*
test_bad_func_case.c
*/
int add(int a, int b) {
return
a+b;
}
void test() {
char *p = (char*)add(1, 13);
}
gcc
-Wbad-function-cast -c test_bad_func_case.c
test_bad_func_case.c: In function
`test’:
test_bad_func_case.c:11: warning: cast does not match function
type
[-Wcast-qual]
当去掉修饰源Target的限定词(如const)时,给出警告。
e.g.
/*
*
test_cast_qual.c