作者介绍:olivialu,架构平台部质量组员工,测试妹子两年来摸爬滚打、踩坑无数,努力从细微处发现问题、提升测试思路和方法,通过构建测试工具,让每个BUG无所遁形。
前言
编译告警有error和warning之分:编译器确定不允许的就认为是error,然后一些违背原则但是编译器又不确定的就定义为warning,所以说warning是编译器为程序员提供的友善建议和意见。但是有warning大部分时候系统都不会出现明显问题,导致很多同学对warning都抱着“warning is okay”的态度,其实warning里是隐藏了一些问题在的,一旦踩坑,查找问题根源确是一件痛苦的事。
下面就简单分享一些老司机经常遇到的告警案例,说说这些warning表明代码可能出现哪些问题。
案例分享
CASE 1: implicit declaration of function 'foo'
含义:隐式声明了函数
可能存在的问题:coredump
代码示例:
猜猜,编译执行后结果如何呢?
上面的 implicit declaration 是最常见的warning之一,这种问题大多数是因为没有包含相应的头文件。编译器是依据header file里申明的函数原型来对调用进行check的,如果没有函数的申明,那么编译器只会抛出”implicit declaration“的warning,而在Link的时候,只要其他lib里面能够找到这样的函数名,那么根据符号匹配就能Link成功。但是,当你运行的时候,假如调用的函数和函数原型不匹配,就会出现coredump,如上面的case所示。所以正确的做法应该是include其他模块的header file,这样如果调用的时候参数类型和个数不匹配便会发生Compile Error。
CASE 2: passing argument 1 of 'foo' from incompatible pointer type
含义:传参类型和声明不一致
可能存在的问题:功能异常
代码示例:
上面的 incompatible type 又是一常见的warning,这样的问题大多数情况下应该是okay的,因为C会进行隐式转换,但是像上面的case估计就踩到雷区了,可能它的输出就未必是你想要的了因为a,b是char类型占1byte,而foo()的两个pointer都是int *,所以在里面进行调换的时候就会发生覆盖的情况,要想知道结果就自己试试吧。
CASE 3: comparison is always true due to limited range of data type
含义:数据类型的位宽导致表达式永远为真
可能存在的问题:死循环、逻辑错误
代码示例:
上面的warning写的很清楚,但是你如果不看估计也未必能发现你是多么的傻,估计在C的第一章节就会讲到常用的数据类型,然后老师还会强调每一种数据类型的长度,char的取值区间是-128 ~ 127,所以这里的<255永远都是ture,这样就产生了你不预期的死循环。
CASE 4: comparison between signed and unsigned integer expressions
含义:无符号数和有符号数之间比较
可能存在的问题:逻辑错误
代码示例:
上面的代码用g++编译会产生一条“comparison between signed and unsigned integer expressions”的警告,上面的隐式转换往往会给你带来非预期的结果,结果会是 -1 > 1,所以要小心此类告警。
CASE 5: ‘xxx’ has virtual functions but non-virtual destructor
含义:类有虚函数却没有虚析构函数
可能存在的问题:资源泄漏
代码示例:
c++建议有virtual函数时,析构函数最好定义为 public & virtual或者 protected & nonvirtual,以防止资源泄漏。如上面的小例子所示,构造时new了一段空间,但析构时没有释放掉,导致出现了内存泄漏。
CASE 6: suggest parentheses around assignment used as truth value
含义:建议加个括号赋值表达式两边
可能存在的问题:逻辑错误
代码示例:
在C语言中,非0即代表TRUE,反之为FALSE。上面的语句会以“= ”前面的值用于最后的判断。但是长期的编程实践告诉我们,人们经常在“=”和“==”的使用上出现手误,所以gcc编译器为此要求我们明确地告诉它是“=”而不是“==”,是故意,而非手误。上面的if语句就少了个“=”号,你发现了吗?
CASE 7: left shift count >= width of type
含义:数据溢出
可能存在的问题:逻辑错误
代码示例:
上面这行代码是有问题的,你发现了吗?x为0,而不是2^32,需要按下面这样写,就不会溢出啦:
CASE 8: statement has no effect
含义:无效语句
可能存在的问题:逻辑错误
代码示例:
这是微云下载功能的一段代码,移位后没有自赋值,会导致4G以上文件无法下载,还好下载时,文件都已经被切割了,不会有大文件,不然上线后又是一个故障。
CASE 9: taking address of temporary
含义:使用临时地址
可能存在的问题:未知行为,高危
代码示例:
上面这段代码是段神奇的代码,gcc 4.1.2 上可以编译通过,但会告警“ taking address of temporary”,执行后的结果是:
可见使用临时地址的输出是不符合预期的,而大部分情况下,这种行为的结果是未知的,所以高版本的gcc会直接error,无法编译通过。
CASE 10: invalid access to non-static data member 'xxx' of NULL object
(perhaps the ‘offsetof’ macro was used incorrectly)
含义:非法访问空对象的非静态成员,可能是错误使用了offsetof宏
可能存在的问题:未知行为
代码示例:
程序中的off_pos是计算变量role在player_t结构中的偏移量的,但offsetof 宏仅限于 standard layout & trival,如a built-in type, pointer, union, struct, array, or class with a trivial constructor。而上述代码中的结构体中的map类型不符合上述约束,所以对其offsetof可能会出现未定义的行为。
CASE 11: cannot pass objects of non-POD type ‘xxx’ through ‘...’; call will abort at runtime
含义:传入non-POD参数,运行时系统将会abort
可能存在的问题:coredump,高危
代码示例:
这种告警一旦发现请立即修改,一旦执行到,就会abort。
所以,我们应当认真对待warning,如果你不以为然,可能不小心就掉坑里了,有时候还很难爬上来。养成良好的习惯,清除warning,让build更干净,让compiler更happy。