目录
1、PC-Lint 概述
2、PC-lint 常见错误列举
3、PC-Lint报告的语法错误
4、总结
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html 为了提高代码质量,我们一般会引入一些代码静态检测工具,比如PC-lint和腾讯的TScanCode等,可以帮我们发现代码中存在的一些潜在问题。之前我们项目中使用了PC-Lint,本文大概地总结一个PC-Lint的常报错误,以及这些错误的解决办法。
1、PC-Lint 概述
PC-Lint是一种C/C++静态代码检测工具,通过扫描静态的代码文件(头文件和源文件)进行分析,不仅可以像IDE编译器那样检查出一般的语法错误,还可以检查出那些表面上完全合乎语法要求,但很可能是潜在的、不易发现的问题或错误,比如数组访问越界、内存泄漏、使用未初始化变量等,这样就可以很好地提高软件的质量。
对于这些bug,如果事先没有检测,在软件运行过程中出了问题,通过走读代码或者使用分析工具去分析,可能会花费很长时间才能找到原因。通过使用PC-Lint进行检测,可以在测试开始前就能检测出这些错误,会给测试和开发节省很多的时间。虽然使用PC-Lint会花费一些时间,但这是完全值得的。
另外,PC-lint还支持Effective C++ / More Effective C++中说描述的各种提高效率和防止错误的方法,对照相应的条款,还可以学习与熟悉这些编程要点,提升我们编码的技能。
2、PC-lint 常见错误列举
下面所列举的常见错误只有ID号与简单的描述信息,如果有需要,可以通过ID号在PC-Lint目录下的MsgRef.html文件中查看详细的描述信息。另外本文对这些错误只进行了一些简单的分析,其中部分错误可以参见《Effective C++》上相应的条款。
1)error 1561: (Warning -- Reference initialization causes loss of const/volatile integrity (arg. no. 1))
PC-Lint发出警告:“第一个参数在初始化时会破坏const属性”,双击警告,定位到代码:
TManufacturerInfo *pManufactureList=NULL;
DWORD dwManufactureNum=g_pcCommData->GetAllManufactory(pManufactureList);
查看一下GetAllManufactory函数的声明:
DWORD GetAllManufactory(const TManufacturerInfo*& ptManufactory) const;
参数是const TManufacturerInfo*&,不希望指针所指向的内容被改变,但是我们传入的参数是TManufacturerInfo *,缺少了const限制,导致我们就可以随意改变指针所指向的内容。
2)error 1401: (Warning -- member ‘XXX::YYY' not initialized by constructor);
成员变量没有被初始化,会造成程序一些不可预知的结果,对于这些危害,大家都比较清楚的,但这还是最常犯的一些错误之一,因为大家在开始写代码的时候或以后的维护的时候还会忘记的。
3)error 1729: (Initializer inversion detected for member 'Symbol' );
成员变量在初始化列表的顺序与在类声明中出现的顺序不一致。C++总是按成员变量在类声明中出现的顺序来初始化成员变量的,如果不一致有可能造成一些隐蔽的bug;例如下面的代码:
class A
{
public:A( ):y(100),x(y){}
private:int x;int y;
};
在成员变量初始化时,由于y(100)写在了x(y)之前,我们很可能认为先执行y(100),然后执行x(y),这样x与y的值都初始化为100,但实际上x(y)首先执行,y这时还没有被初始化,x就被初始化为一个随机值。
4)error 1540: (Warning -- Pointer member ' XXX::YYY ' neither freed nor zeroed by destructor -- Effective C++ #6)
在析构函数中一定要把成员指针变量置为空指针,以免产生野指针,并且查看程序中是否有申请空间的操作,如果有一定要用delete或delete[]释放空间,如果没有则一定不要释放该指针指向的空间,把别人申请的空间释放掉,很可能引起错误。
5)error 545: (Warning -- Suspicious use of &)
意思是“错误的使用了&”,定位到代码:
int array[100];
memset(&array, 0, sizeof(array));
原来我们多用了“&”, 改成:
memset(array, 0, sizeof(array));
就行了。其实array与&array都指向了同一个地址,但是要注意 array 和 &array 的类型是不同的。array 相当于 &array[0],而 &array 是一个指向 int[100] 的指针,类型是 int(*)[100]。array + 1指向数组的第二个元素,而&array + 1指向的地址已经超出了本数组的地址范围了。
6)error 616: (Warning -- control flows into case/default)
error 744: (Info -- switch statement has no default)
error 788:(Info -- enum constant 'Symbol' not used within defaulted switch)
这3个警告都是与switch语句有关,都是在使用switch时最容易犯的错误,第一个是当一个case语句后面没有用break,程序会继续执行下一个case语句;第二个是没有写default语句;第三个是当使用enum类型作为switch值时,至少有一个enum变量没有出现在case语句中。
7)error 613: (Warning -- Possible use of null pointer ' 'XXX::YYY ' in left argument to operator '->')
指针有可能为空,使用空指针会使程序崩溃的,在写代码时遇到使用指针时最好还是用if语句判断一下,或者assert断言一下。
8)error 1763: (Info -- Member function 'CXXX::YYY(void) const marked as const indirectly modifies class)
成员函数被声明成const,但是成员变量的值有可能被间接改变,看一下函数的实现:
HTREEITEM CMGTreeCtrl::GetHitItem() const
{return m_TvhitLastClick.hItem;
}
函数被声明成const,表示这个函数不能改变成员变量的值,但当函数返回成员变量的指针或句柄时,成员变量的值就能被外部操作改变,这时要为返回值加上const,以便告诉调用者,不希望返回的指针或句柄所指向的内容的被改变。
9)error 773: (Info -- Expression-like macro 'LIST_DEVICE_MSG_ON_PAGE_CHANGE' not parenthesized)
定位到代码:
#define LIST_DEVICE_MSG_ON_PAGE_CHANGE WM_USER+1
定义宏时没有加括号,当程序预编译时,用WM_USER+1替换到代码中有可能产生非预期的结果,比如:
LIST_DEVICE_MSG_ON_PAGE_CHANGE - LIST_DEVICE_MSG_ON_PAGE_CHANGE
我们当然希望结果是0,但得到的结果却是2。
10)error 424: (Warning -- Inappropriate deallocation (delete) for 'new[]' data -- Effective C++ #5)
使用new[]申请空间,但却使用delete释放空间,造成内存的泄漏,在使用时new与delete配对,new[]与delete[]配对。
11)error 1506: (Warning -- Call to virtual function ' 'XXX::YYY(void)' within a constructor or destructor)
在构造函数或者析构函数中千万不要调用virtual函数,因为在base class构造函数中,virtual函数不会调用derived class中的虚函数,不会带来你所预想的结果。如果一定要使用virtual函数,请使用“类名::virtual函数”表示调用自身的函数,避免造成误解。
12)error 527:(Warning -- Unreachable code at token 'XX')
不可到达的代码。可能是因为前面return掉了,后面的代码就永远不可能执行。通常是因为我们暂时屏蔽掉了某些功能但暂时没有删除其代码导致。可以用#if 0 来屏蔽之:
#if 0 //写出屏蔽原因
代码
#endif
13)error 1925:(Note -- Symbol 'XXX::YYY' is a public data member)
error 1536:(Warning -- Exposing low access member 'CXXX::m_XX')
在类设计时应该将所有成员变量为private,而不是public,因为:
(1)更精确的控制对成员变量的处理。如果成员变量是public的话,每个人都可以读写它,但如果时private的话,我们就可以通过是否提供setterXX、getterXX实现对成员变量的不准访问,只读,只写,读写多种方式。
(2)封装性,将成员变量隐藏在函数接口的背后,为以后修改接口提供方便。
14)error 578: (Warning -- Declaration of symbol ' XXX ' hides symbol ' XXX' (line 891))
当前声明的XXX覆盖了891行声明的XXX,在一个变量的作用域下再次声明了一个同名的变量在语法上当然没有什么错误,但是这样会造成歧异,容易混淆,给别人阅读代码造成困难,不利于以后的维护。
15)error 661: (Warning -- Possible access of out-of-bounds pointer (22 beyond end of data) by operator '[')
数组下标可能越界。
16)error 774: (Info -- Boolean within 'if' always evaluates to False)
If判断语句的值始终为FALSE或者TRUE。
17)error 524: (Warning -- Loss of precision (arg. no. 3) (double to int))
error 732: (Info -- Loss of sign (arg. no. 1) (char to unsigned int))
error 713: (Info -- Loss of precision (arg. no. 1) (unsigned long to long))
error 573: (Warning -- Signed-unsigned mix with divide)
error 574: (Warning -- Signed-unsigned mix with relational)
error 747: (Info -- Significant prototype coercion unsigned long to double)
error 737: (Info -- Loss of sign in promotion from int to unsigned long)
error 734: (Info -- Loss of precision (arg. no. 2) (31 bits to 7 bits))
error 570: (Warning -- Loss of sign (initialization) (int to unsigned long))
error 569: (Warning -- Loss of information (assignment) (32 bits to 31 bits))
error 736: (Info -- Loss of precision (initialization) (55 bits to 32 bits))
与精度丢失和符号错误相关的一些警告。
18)error 539: (Warning -- Did not expect positive indentation from line 529)
代码格式没有对齐,这个时候,先选中没有对齐的代码,然后使用“ALT+F8”键,就可以对齐了。
19)error 715 (Symbol XXX not referenced)
error 752 (local declarator XXX not referenced)
error 550 (Symbol XXX not accessed)
error 529 (Symbol XXX not subsequently referenced)
error 750 (Info -- local declarator 'XXX' macor not referenced)
error 751 (Info -- local typedef 'XXX' not referenced)
代码中声明了一些没有用过的东东。
20)error 685: (Warning -- Relational operator '>=' always evaluates to True)
error 775: (Info -- non-negative quantity cannot be less than zero)
error 568: (Warning -- non-negative quantity is never less than zero)
if 语句中判断一个整数是否小于零, 而正数不可能小于零:
DWORD dwValue = XXX;
if( dwValue >= 0 )
if( dwValue < 0 )
21)其他错误
以下的警告信息也是比较常见的信息,这里只做一些简单的列举:
error 818: (Info -- Pointer parameter 'Symbol' (Location) could be declared as pointing to const)//建议为指针参数加上const属性
error 1762: (Info -- Member function 'Symbol' could be made const)//建议为成员函数加上const属性
error 1746: (Info -- parameter could be made const reference)// 函数参数可以成为const引用
error 1764: (Info -- Reference parameter 'XX' could be declared const ref) //建议为引用参数加上const属性error 1732: (Info -- new in constructor for class 'CXXX' which has no assignment operator)//在类的构造函数中使用new申请了一块内存,但没有提供operator =。
error 1733: (Info -- new in constructor for class 'CXXX' which has no copy constructor )// 在类的构造函数中使用new申请了一块内存,但没有提供copy constructor。
注:在构造函数中使用new申请一块内存,可以推测在类中应该有一个成员变量指针指向该内存。当使用该类的一个对象去克隆另一个对象时,如果该类没有声明copy constructor或operator =,编译器将会自动生成一个默认的copy constructor或operator =,但默认的copy constructor与operator =都是“浅拷贝”,那么这2个对象的指针就指向了同一块内存地址,当一个对象析构时这块内存会被释放,另一个对象就会被破坏。error 749: (Info -- local enumeration constant not ref)//定义了枚举常量却没有使用。
error 506: (Warning -- Constant value Boolean)//布尔值是个常量,导致if()或者while()每次都执行同样的路径。
error 669: (Warning -- Possible data overrun for function memcpy)//可能copy的数据过大
error 765: (Info -- external XXX could be made static)//只用在一个文件中的变量应当声明为staticerror 429: (Warning -- Custodial pointer 'pXXX'has not been freed or returned)//内存可能没有释放
error 1774: (Info -- Could use dynamic_cast to downcast polymorphic type 'CXXX')//建议使用dynamic_cast将一个父类转换成子类。
error 730: (Info -- Boolean argument to function)// 函数参数非 Boolean类型,但却传入了一个Boolean类型的数。
error 1551: (Warning--function may throw exception 'Name' in destructor 'Symbol' ) //析构函数抛异常
3、PC-Lint报告的语法错误
以下列举的警告都是语法错误,基本上都是类型错误、重复定义、没有定义之类的东西,由编译器检查就OK了,不用用PC-lint来检查,一般情况下可以把这些信息屏蔽掉。
error 40: Undeclared identifier//标识符未定义
error 1055: Symbol 'XXX' undeclared, assumed to return int//函数没有定义,假定返回值为int
error 1015: Symbol 'XXX' not found in class //没有定义成员变量'XXX'
error 1013: Symbol 'XXX(OnInitialUpdate)' not a member of class 'YYY(CListView)' //'XXX’不是另一个成员函数
error 746: call to function 'XXX(IsKindOf())' not made in the presence of a prototype) //没有发现函数原型
error 38: Offset of symbol 'XXX' inconsistent (conflicts with other cpp)//
error 113: (Error -- Inconsistent enum declaration)//枚举变量声明的顺序不一致(另一个模块有同名的枚举类型引起)
error 148: Error -- member 'Symbol' previously declared at Location//变量已经声明过了,重复声明
error 1039: (Error -- Symbol 'XX::YY()' is not a member of class 'XX')// YY()不是'XX'的成员函数
error 322: (Error -- Unable to open include file 'xx.h')//不能打开头文件
error 7: (Error -- Unable to open include file 'xx.h') //不能打开头文件
error 1074: (Error -- Expected a namespace identifier)//希望使用命名空间标识符
error 31: (Error -- Redefinition of symbol "XXX" compare with line)//重复定义
error 14: Symbol 'XXX' previously defined//变量重复定义
error 1032: (Error -- Member 'XXX' cannot be called without object)//非static变量只能通过类的一个对象调用
error 58: (Error -- Bad type)指针不能与某个数比较
error 63: (Error -- Expected an lvalue)//需要是一个左值
error 115: (Error -- struct/union not defined)//结构体后者枚举类型没有定义
error 770: (Info -- tag 'Symbol' defined identically at Location)//定义了相同的结构体或者联合体
error 64: type mismatch //类型不匹配
error 526: (Warning -- Symbol 'XXX'没有定义),变量没有定义
error 833: (Info -- Symbol 'XXX' is typed differently in another module)//变量在另一个模块中被定义成不同的类型
error 118: (Error -- Too few arguments )//传入的参数太少
error 55: (Error -- Bad type)//类型错误
error 1046: (Error -- Member 'CXXX::XX', referenced in a static function, requires an object)//非static变量只能不能被static函数调用
4、总结
由于人工Code Review工作很耗精力,我们在程序进行编译测试前使用PC-lint进行代码检查,可以预先发现和规避很多错误,提高代码质量,节省测试时间,规范编码行为。