个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)
近来做代码静态检视,发现除了比较严重的内存泄漏、逻辑错误等问题外,代码的细节、规范性问题很多。
静态工具检查出的点,经不起推敲,会发现代码逻辑中确实有许多不严谨的地方。
1. 在null 检查前解引用
如下面的例子:pT != nullptr判空前,已经有使用pT->a()了,判空就不起作用了,如果有异常,已经崩溃了。
class TestClass { int a; int b; };
TestClass* pT = getTestClass(); int c = pT->a(); … if (pT != nullptr) { … } |
2. 在null 检查后解引用
如下面的例子:
pT != nullptr判空后做了某些处理;但在随后的处理中,再使用pT时,没有做判空处理。
在这种情况下,如果pT为空,就会崩溃在下面,判空处理的范围没有覆盖全。
TestClass* pT = getTestClass(); if (pT != nullptr) { … } int c = pT->a; |
3. 显式 null 被解引用
这种不易察觉,在某个分支对元素赋值,另一分支未赋值,这种情况对于未赋值情况可能会出现崩溃。
如下面的例子:
TestClass* pT = nullptr; if (y == 0) { pT = getTestClass(); } int c = pT->a; |
4. 逻辑死代码
有简单的,例如这种,最后一行无论如何走不进来:
if (xxx) { return 1; } else { return 0; } return 0; |
逻辑上自相矛盾的地方,例如上面if包含的逻辑,else又做判定,如下面这种
if (pA == nullptr || pB == nullptr) { b = 1; } else { if (pB == nullptr) { do something } } |
5. 未检查的 dynamic_cast
我们已知dynamic_cast对于类型是会做相应的检查,如果类型不在范围内的话,会返回false的,可是对于dynamic_cast,我们有时没有对返回的值做结果判定,这就有一定风险了;
例如:下面的show1,show2没有做dynamic_cast结果判定,当传入的值不是对应类型的时候,就会产生崩溃了。
class A {}; class B : publice A {void showB();}; class C: public A {void showC();}; void show1(A* pA) { B* pB = dynamic_cast<B*>(pA); pB->showB(); } void show2(A* pA) { B* pC = dynamic_cast<C*>(pA); pC->showC(); } |
6. 混合了枚举类型
枚举类型,一般我们使用的时候,是各个对应各个的,但有时,因为值一样,我们可能就存在混用的情况。这个也是一种风险。
例如:getTypeA() 定义的返回是EnumTypeA,但判断时,使用了enumBDefault,虽然值是一样,但类型实际是不对应的,存在风险,万一哪天改了呢。
enum EnumTypeA { enumADefault = -1, enumA1 = 0 }; enum EnumTypeB { enumBDefault = -1; enumB1 = 0 };
EnumTypeA getTypeA() {…}
if (getTypeA() == enumBDefault) { do something } |
7. 未初始化的标量字段
对于int/boo/指针/double等基本的数据类型,都是没有构造函数的,如果在struct/class中定义了,构造函数都需要赋值的,否则就会存在随机的情况。
例如下面的例子,getA函数,访问了TestClass p->a变量,a变量是有初值的,但是b变量是没有初值的,b的值实际上是随机的,这种情况就是一种潜在的风险。
我们无法确保所有的类似getA函数(当前不使用b)未来不会使用b变量作为参考。而且随机变量传递本身也是不建议出现的,好的做法是在class构造函数中,把基本数据类型变量都初始化了。
class TestClass { int a; int b; void * p TestClass : p(nullptr), a(0){} };
void getA(TestClass* p) { return p->a; };
TestClass* pT = getTestClass(); int c = getA(pT); |
8. 赋值而不是比较
从标题上就可以看出,问题比较清晰
例如:放入if语句的是一个赋值语句,这种一般都是写错了。
if (n = 0) { do something; }; |
9. 常量表达式结果 (如 &&与||用错)
问题也比较清晰
例如:两处都是有问题的,第一处if判定恒true,第二处恒false
if (n != 0 || n != 1) { do something; }; |
if (n == 0 && n == 1) { do something } |
10. 两侧相同
出现 if (pT != nullptr && pT != nullptr)这种情况,这种情况通常是想写别的。
如下面的例子:
if 中使用 pT1 != nullptr判断两次,但从上下文看,实际是也想判定pT2 != nullptr,这种情况是我们需要注意的。
TestClass* pT1 = getTestClass(xx); TestClass* pT2 = getTestClass(yy); if (pT1 != nullptr && pT1 != nullptr) { pT1 do something pT2 do something } |
11. 适用于不同分支的相同代码
if与else的内容一致
例如:这种是if/else的处理一致,那还用if判定就没有意义了
if (a == 0) { b = 1; } else { b = 1; } |
个人随笔 (Owed by: 春夜喜雨 http://blog.csdn.net/chunyexiyu)