你需要的代码静态检查

代码静态检查

使用cppcheck给工程代码做静态检查,主要发现了以下几个问题:

1. 使用C风格的类型转换

警告如下:

C-style pointer casting detected. C++ offers four different kinds of casts as replacements: static_cast, const_cast, dynamic_cast and reinterpret_cast. A C-style cast could evaluate to any of those automatically, thus it is considered safer if the programmer explicitly states which kind of cast is expected. See also: https://www.securecoding.cert.org/confluence/display/cplusplus/EXP05-CPP.+Do+not+use+C-style+casts.

应该使用C++提供的static_cast, const_cast, dynamic_cast 和 reinterpret_cast 做类型转换,明确转换的类型。

2. 迭代器使用后置叠加(叠减)运算符

警告如下:

Prefix ++/-- operators should be preferred for non-primitive types. Pre-increment/decrement can be more efficient than post-increment/decrement. Post-increment/decrement usually involves keeping a copy of the previous value around and adds a little extra code.

迭代器前置++和后置++ 的运行效率是不同的,前置++效率更高,因为后置运算符需要做一个临时的类对象拷贝。

3. 直接在函数参数中使用C风格字符串。

警告如下:

The conversion from const char* as returned by c_str() to std::string creates an unnecessary string copy. Solve that by directly passing the string.

比如一个函数的参数类型是strng,调用时实参直接使用了C风格的字符串,于是就会有以上提示,主要还是因为这里会造成一个不必要的字符串拷贝,降低运行效率。

4. 使用无用的find

很多时候,我们会写下面的find代码,值不等于-1则说明找到了该字符串,

if(std::string::npos != dataStr.find("what"))
    //do something

该代码段会导致报警:

Either inefficient or wrong usage of string::find(). string::compare() will be faster if string::find's result is compared with 0, because it will not scan the whole string. If your intention is to check that there are no findings in the string, you should compare with std::string::npos.

代码本身不会出错,但是效率上是不被认可的,如cppcheck所说,如果你希望检查某个字符串是不是某个期望的字符串,那你应该使用compare函数,因为这样更快。

5. 函数参数使用传值而不是传引用

警告如下:

Parameter 'strShowTime' is passed by value. It could be passed as a const reference which is usually faster and recommended in C++.

C++给了引用传参,而你不使用,那就是你的问题了。

6. 构造函数没有使用初始化成员变量列表,而是习惯于写入构造函数体

警告有:

Member variable 'CActiveComboItemXml::m_phorActivecombo' is not initialized in the constructor.

或者如:

When an object of a class is created, the constructors of all member variables are called consecutively in the order the variables are declared, even if you don't explicitly write them to the initialization list. You could avoid assigning 'm_strHrefOnPanReady' a value by passing the value to the constructor in the initialization list.

这个问题很普遍,因为很多程序员不习惯在构造函数的初始化列表中初始化成员变量,有的是直接忽略掉,有的则是在构造函数的函数体中去使用赋值运算符赋值,可是这个时候已经不是初始化了,而是赋值阶段了。这是个很危险的习惯!

7.使用memset清空含有string(wstring)类型成员的结构体

警告为:
Using 'memset' on struct that contains a 'std::wstring'. [memsetClass]

在C语言中,使用memset清空内存是常用操作,在C++中,和malloc一样,使用memset直接操作内存是有很大风险的,因为它们都只是在内存层面做了改动,但是对类对象本身却没有任何行动,也就是说,他们不会调用类对象的构造或者析构函数,如果我们的结构体中含有string类型的成员,直接使用memset很可能导致内存泄漏!

这里涉及到一个POD参考的概念。如果一个类不是POD的,那么就不应该使用如mem*,malloc等内存操作函数,否则,我们将得不到我们想要的东西,甚至引发错误。

举一个很简单的例子:

struct struTest
{
string str1;
};

void TestStructString()
{
    struTest tt;    
    size_t sizestru = sizeof(tt);
    tt.str1 = "this OK";
    struTest* tt2 = new struTest;
    memcpy(tt2,&tt,sizestru);
    cout << "tt2 str1 is:"<<tt2->str1 <<"\n";
    delete tt2;
	//程序奔溃!!!
}

测试程序以memcpy函数为例,运行本程序,虽然tt2可以正常的将str1拷贝进来,但是最后程序奔溃了!!
想想原因是为何? 程序崩溃在tt的析构函数中,因为无法探究memcpy到底做了哪些具体操作,所以我们可以猜测它是将tt的内存区域完整的拷贝到了tt2内,但是,tt2中的strin类型的成员str1并没有完成自己的构造,而是通过内存拷贝的方式完整的使用了tt的数据,那么执行完delete ,tt2的str1也会析构自身,这时其析构的其实是tt的str1。等到tt再去析构自己时,奔溃就发生了!以上只是自己的猜测。可能不是事实,但是,当我把delete tt2注释后,崩溃就消失了,这也能证明自己上面的论述。

问题怎么处理?

在C++中,除非是明确的POD类型,否则放弃使用mem*系包括malloc等传统C内存操作函数。

首先,我们需要弄明白,我们为什么需要使用memet,因为我们需要将这个结构体的数据清零,所以我们真正需要的是一个结构体(类)的构造函数!在类中写一个结构体,将类里的所有成员变量进行列表初始化就可以解决这个问题了。
话说回来,就好像,我们在写C代码时, 如果结构体某个成员类型需要是结构体类型,我们使用了该结构体指针一样,我们同样可以使用string类型的指针来表示该成员类型。毕竟在VS2010环境下,一个string类型的内存占用就是32byte,而一个string*只有4byte。如果担心hold不住指针类型,可以使用智能指针来折中,如shared_ptr,内存的占用将减小到8。事实上,使用智能使用已经是一个最优方案了。

这几个问题大量出现在cppcheck的问题列表中,是我们经常犯的编程问题,应从代码风格上进行规避。
当然了,可能的错误(警告)是由不当的编码风格和不扎实的C++编码基础导致的,通过静态检查我们自己的代码,可以最大层度的写出易读且不容易出错的代码。

推荐大家使用cppcheck!

posted @ 2018-12-03 22:19  Lckfa  阅读(2899)  评论(0编辑  收藏  举报