GCC编译警告选项的学习

GCC有很多的编译选项,警告选项;指定头文件、库路径;优化选项。本文针整理一下GCC的警告选项,主要依据http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html文档,并加上自己的一点小小经验。

一、总概

-w

禁止编译警告的打印。这个警告不建议使用。大约2012年底,公司代码进行一次大重构,另外从Codeblock集成开发环境转向Makefile管理,Makefile里面默认使用了-w,因而代码一直没有警告,今年个别项目开发中发现一些代码笔误导致的BUG,而这些问题可以从编译警告中知道。前几个月,领导安排我来fix这些警告。为了自己,为了后人,不建议使用-w选项。

-Werror

将所有的警告当成错误处理。此选项谨慎建议加上。有的开源库警告很多(大名鼎鼎的ffmpeg也有很多警告呢),一一改掉耗时耗人力,必要性也不大。最后,公司代码加入了一个开源库,里面有很多代码警告,可能领导又安排我来fix了。

-Wfatal-errors

遇到第一个错误就停止,减少查找错误时间。建议加上。很多人遇到错误,没有意识到从第一个开始排查。不管是编译错误,还是程序运行出错,从最开始的错误查起,是个好的做法。

-Wall

开启“所有”的警告。强烈建议加上,并推荐该选项成为共识。如case语句没有default处理,有符号、无符号处理,未使用变量(特别是函数有大量未使用的数组,占用栈空间,测试发现,开辟一个未使用的8MB的数组,程序有coredump),用%d来打印地址,或%s打印int值,等,都可以发出警告。

-Wextra

除-Wall外其它的警告。建议加上。 在GCC编译时,加上必要的警告选项,可以避免很多低级错误引发的问题,我就在实际工程代码中遇到用“==”来赋值,我自己写的代码也出现过把“=”当成判断的。但是,有些错误却不是用GCC选项能解决的。比如一般项目都会自定义调试信息打印函数,但在处理可变参数类型时,往往不注意。可参考文章《一个可变参数类型检查的示例》。

二、细化

上面只是大概讲几个重要的选项。由于GCC的警告选项太多了,下面尽自己能力写一下。
-Wall选项,顾名思义,就是“所有”的意思,它包括: -Wall包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-Waddress   
-Warray-bounds=1 (only with -O2)
-Wc++11-compat -Wc++14-compat
-Wchar-subscripts
-Wenum-compare (in C/ObjC; this is on by default in C++)
-Wimplicit-int (C and Objective-C only)
-Wimplicit-function-declaration (C and Objective-C only)
-Wbool-compare
-Wduplicated-cond
-Wcomment
-Wformat
-Wmain (only for C/ObjC and unless -ffreestanding)
-Wmaybe-uninitialized
-Wmissing-braces (only for C/ObjC)
-Wnonnull
-Wopenmp-simd
-Wparentheses
-Wpointer-sign
-Wreorder
-Wreturn-type
-Wsequence-point
-Wsign-compare (only in C++)
-Wstrict-aliasing
-Wstrict-overflow=1
-Wswitch
-Wtautological-compare
-Wtrigraphs
-Wuninitialized
-Wunknown-pragmas
-Wunused-function
-Wunused-label
-Wunused-value
-Wunused-variable
-Wvolatile-register-var

但不要被它的表面意思迷惑,要不,怎么还会有-Wextra呢。-Wextra包括(有几个选项重复了,不懂原因):

1
2
3
4
5
6
7
8
9
10
11
12
13
-Wclobbered  
-Wempty-body
-Wignored-qualifiers
-Wmissing-field-initializers
-Wmissing-parameter-type (C only)
-Wold-style-declaration (C only)
-Woverride-init
-Wsign-compare
-Wtype-limits
-Wuninitialized
-Wshift-negative-value
-Wunused-parameter (only with -Wunused or -Wall)
-Wunused-but-set-parameter (only with -Wunused or -Wall)

-Wchar-subscripts

使用char类作为数组下标(因为char可能是有符号数) -Wcomment: 注释使用不规范。如/* */注释中还包括/*。我在项目源码发现过,不止一处。

-Wmissing-braces

括号不匹配。在多维数组的初始化或赋值中经常出现。下面a没有完整被初始化,b完整初始化:

1
2
int a[2][2] = { 0, 1, 2, 3 };
int b[2][2] = { { 0, 1 }, { 2, 3 } };

-Wparentheses

括号不匹配,在运算符操作或if分支语句中,可能会出现此警告。
如“a&&b||c^d”会出现警告。下面代码片段也会有警告

1
2
3
4
5
6
7
{
if (a)
if (b)
foo ();
else
bar (); // 这个else实际是if (b)的分支,不是if (a),因此,要用括号来表明其属于哪个分支
}

这类bug隐藏得深,建议显式地加上括号。

-Wsequence-point

如出现i=i++这类代码,则报警告。-Wall默认有该警告

-Wswitch-defaultcase

没有default时,报警告

-Wunused-but-set-parameter

设置了但未使用的参数警告

-Wunused-but-set-variable

设置了但未使用的变量警告

-Wunused-function

声明但未使用函数

-Wunused-label

未使用的标签,比如用goto会使用label,但在删除goto语句时,忘了删除label。

-Wunused-variable

未使用的变量

-Wmaybe-uninitialized

变量可能没有被初始化。特别是在有if语句或switch语句中,最好在声明变量时加上初始化。
下面代码片段中,当y不是1、2、3时,x没有明确的值,是不安全的。

1
2
3
4
5
6
7
8
9
10
11
12
{
int x;
switch (y)
{
case 1: x = 1;
break;
case 2: x = 4;
break;
case 3: x = 5;
}
foo (x);
}

-Wfloat-equal

对浮点数使用等号,这是不安全的。

1
2
3
4
5
6
7
{
float d = 2.0;
if (d == i)
{
...
}
}

-Wreturn-type

函数有返回值,但函数体个别地方没有返回值(特别是有if判断,可能忘记在else添加返回值)。

1
2
3
4
5
6
7
8
int foo()
{
if(a==1)
{
return ok;
}
// no return here
}

-Wpointer-sign

指针有符号和无符号的错误传参。如函数使用unsigned char,但传入char指针。

-Wsign-compare

有符号和无符号比较。

-Wconversion-null -Wsizeof-pointer-memaccess

在sizeof中经常出现,下面代码片段中,this为指针,4字节,无法保证完整初始化类。

1
memset(this, 0, sizeof(this));

-Wreorder

C++出现,构造函数中成员变量初始化与声明的顺序不一致。

-Woverflow

范围溢出。

-Wshadow

局部变量覆盖参数、全局变量,报警告

参考资料: http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

李迟 2015.12.11 周五 夜