分享

高质量编程之编译警告级别

 3D建模仿真 2014-01-26

作为程序员不但要会编程,还要编好程,即编写高质量的程序。评价程序质量的指标有很多(正确性、可靠性、有效性、可扩展性、可维护性……),用于保证软件质量的方法和技巧也非常多。本篇只讲述在编码阶段,如果通过设置编译警告级别来提高程序的质量,其目的是减少程序错误、提高程序的可维护性,进而提高软件开发效率。为了达到这个目的程序员需要:在编译程序时将编译警告级别调至最高级别!

下面主要以VC++编译器为例来说明如何设置编译警告的级别和为什么要将编译警告级别设置为最高。需要说明的是,由于本人也处于学习过程中,某些理解还不够透彻,还请读者朋友们多多指教!

1. 警告信息级别&编译警告级别

对经常编程的老手来说,编译警告级别的设置简直是小菜一碟。但我仍然坚持阐述一下如何设置,一方面是为新手指路,另外一方面是我觉得有相当一部分程序员(特别是学生)很少主动去改变程序的编译警告级别。

1.1.    警告信息级别

VC++定义了众多的编译警告消息(从C4001到C4999进行编号),并将这些警告消息根据其危害性分为4个等级:L1到L4(将编号在MSDN中查询就能看到某个警告信息的级别)。其中属于L1级别的警告危害性最大(出现这类警告的程序语句通常都意味着程序错误),L2级别的警告次之,以此类推。

1.2.    编译警告级别

编译警告级别是用来控制编译时产生警告消息数目多少的一种选项,也就是说在不同的编译警告级别下,编译器能报告的警告消息数目是不一样的。与警告信息的4个等级相对应,VC++编译器的编译警告级别包含了六个级别:None,Level 1,Level 2,Level 3,Level 4,Warning As Error。其中None表示不报告任何警告信息,Level 1表示只报告属于L1级别的警告,Level 2表示只报告L1和L2级别的警告,以此类推,Level 4能报告L1,L2,L3,L4级别的警告,而Warning As Error将所有警告信息作为编译错误来对待!需要说明的是:在VC++中,编译警告级别只对源文件起作用(包含头文件),而对目标文件(.OBJ)不起作用。

1.3.    如何指定编译警告级别

在利用VC++编译C/C++程序时,可以通过3种方式来指定编译警告级别:

1、  在继承开发环境中指定:

点击菜单栏中的“Project”菜单项,在弹出菜单中选择“Settings”,在打开的“ProjectSettings”对话框中选择C/C++标签,并在“Category”列表框中选择“General”,最后在“Warning level”列表框中选择一个编译警告级别(None,Level 1,Level 2,Level 3,Level 4),如果要选择Warnings as errors警告级别,就勾选“warning level”标签下方的“warnings as errors”复选框(见图1中的第二个红色矩形框),这时就可以把相应的警告级别的警告信息当做错误来对待。如下图所示:指定编译警告级别为Level 3,并将编译警告按编译错误对待(此时L1,L2,L3级别的警告信息均被报告,并被当做编译错误!)。值得注意的是图1中的第三个红色矩形框内的内容,它们其实对应到刚才所设定的编译警告级别:“/w3”与Level 3对应,“/wx”与warnings as errors对应,它们与在命令行指定编译警告相关。

图 1

2、  在命令行指定:

有的人喜欢在命令行进行源程序的编译,此时如何设置编译警告级别呢?其方法是在编译指令最后加上一个编译警告级别的参数,该参数可以是“/W 0”、“/W 1”、“/W 2”、“/W 3”、“/W 4”、“/W x”中的一种(注意是大写的W,W后有一个空格),它们分别与None,Level 1,Level 2,Level 3,Level 4,Warnings as errors对应。比如要在命令行以Level 3级别编译源程序main.cpp可以如下实现:cl main.cpp /W 3

3、  在源文件中指定:

前面讲述了两种指定编译警告级别的方法,其中第一种方法对当前开发环境中的所有程序有效,而第二种方法只对指定的源文件有效。这里再介绍一种更加灵活的方式:用#pragma warning(push,n)和#pragma warning(pop)配合来控制某段源程序的编译警告级别,其中n取值1,2,3,4,分别对应到Level 1,Level 2,Level 3,Level 4。注意这种方式无法指定None和warnings as errors编译警告级别!比如有一个函数f( ),其中存在L4级别的警告错误,因此你可以通过如下的方式指定该函数代码的编译警告级别为Level 3这样,就不会出现警告消息:

#pragma warning(push,3)             //指定编译警告级别为Level 3

void f(/*……*/)

{

/* codes……*/

}

#pragma warning(pop)          //恢复之前的编译警告级别

2. 为何使用最高级别警告

这里将通过实例来阐述使用最高编译警告级别的好处,由于水平有限,举例或许不够严谨,还望指正。

2.1.    示例程序

 

测试程序1

测试程序2

测试程序3

 

 

 

 

#include "stdafx.h"

int a;

int main(int argc, char* argv[])

{

         int b;

         int* p;

         *p = b;

     int* c;

         return 0;

}

#include "stdafx.h"

#pragma warning(push,3)

int a;

int main(int argc, char* argv[])

{       

int b;

         int* p;

         *p = b;

     int* c;

         return 0;

}

#include "stdafx.h"

#pragma warning(push,4)

int a;

int main(int argc, char* argv[])

{       

int b;

         int* p;

         *p = b;

     int* c;

         return 0;

}

warning C4700: local variable 'b' used without having been initialized

warning C4700: local variable 'p' used without having been initialized

warning C4101: 'c' : unreferenced local variable

warning C4700: local variable 'b' used without having been initialized

warning C4700: local variable 'p' used without having been initialized

warning C4101: 'c' : unreferenced local variable

warning C4100: 'argv' : unreferenced formal parameter

warning C4100: 'argc' : unreferenced formal parameter

warning C4700: local variable 'b' used without having been initialized

warning C4700: local variable 'p' used without having been initialized

warning C4101: 'c' : unreferenced local variable

 

通过上面可以看到,测试程序1,2在编译时的警告信息是一样的: C4700和4101,原因是我的VC++6.0的默认编译器警告级别是Level 3。测试程序3编译时得到了更多的警告信息:C4100。下面对这些警告进行分析:

2.2.    分析警告&高警告编译级别的好处

2.2.1. C4700警告

其含义是:使用了未初始化的局部变量(包含普通变量和指针变量)。它属于L1级别的警告,它通常都预示着该程序将很可能发生错误。在这里的实例中,引起该警告的语句百分之99.9999……会导致程序错误:变量b的使用在运行期不会出错,但是会得到不可预知的结果,因为b是非静态(static)的局部变量,因此编译器不会对其自动初始化(关于那些变量会自动初始化,绝大多数C/C++教程都有介绍);指针变量p的使用会直接在运行期出错。

编译程序前确信编译警告级别不是“None”!假如编译时使用的编译警告级别是None,那么前面的C4700警告都不会被报告!于是程序会在运行时就会出错,并且这类错误对新手来说还很难改正。或许大多数人都觉得自己肯定不会将编译警告级别设置成None,是的,实际情况确实如此,因为大多数人(特别是学生)几乎没有关心过编译警告级别的设置问题。但是您不能不排除某些新手不小心修改了开发环境的设置后导致警告编译级别变成了“None”,您也不能排除某些自信心异常膨胀的程序员主动将编译警告级别设置成“None”!这里,再次呼吁:编译程序前确信编译警告级别不是“None”!

2.2.2. C4100警告

可能有不少人会觉得测试程序3中的两个C4100警告很陌生,这不奇怪,我用了VC++6.0近3年了,今天也是头一回发现。如果您至今还没发现这样的警告,也别气馁,这不能说明我们就不是学习编程的料,世界上的知识太多太多,咱们有不会的是理所当然的。但是,咱们不能满足于现状,要有强烈的求知欲!很多人(包括以前的我),编程时都无视一切警告,只要程序能运行,能有结果输出他就满足了,更有甚者连结果是否正确都不去探究!这样学习编程就是不对的了。

扯远了,接下来看看C4100警告:它是说main函数的两个参数没有被引用。这个警告其实是无关紧要的,它属于L4级别警告,也就是说只有当警告级别设置为Level 4时它才出现,当然它的危害性相对其他级别的警告就要低一些。我相信绝大多数人写控制台程序都没怎么用这两个参数!我再次武断的相信大多数写过控制台程序的人都没对main中这两个参数进行过处理,事实上,对这两个警告置之不理并不会造成程序错误。

那么,是不是说C4100警告就是多余的呢?非也,严格上说,上面的三个测试程序中main函数的参数都应该删除(这对应到一条编程准则:删除冗余的函数形参和局部变量)。为什么这么说呢?我们来假设这样一个场景:您在看别人写的源程序时发现某个函数的某些参数在函数体中从来没使用过。此时,你是否怀疑过该函数的正确性?我想正常人都要怀疑这个函数体中某些代码是不是被删除了(您会认为这些代码引用了那个未被引用的参数)。而事实上该函数可能100%地实现了它的所有功能,那个参数本来就是该函数的作者无意中多加上去的。假设编译警告级别不是最高的Level 4,那么这里的C4100警告就不会被报告,那么您就很难发现这些没有被使用的函数形参,从而导致这些函数让人难以理解(甚至无法理解),最终导致程序的可读性、可理解性、可维护性差!

2.2.3. C4101警告

其含义是:存在未被使用的局部变量。该警告属于L3级别。引起该警告的代码(未被使用的局部变量)也应该被删除,否则它将影响程序的可理解性和可维护性——其道理跟C4100一样!

2.2.4. 其他警告

MSDN中定义的警告从C4001到C4999。是不是我们要对这些警告都有所了解呢?非也,每一个警告都对应到特定的级别,同时也预示着出现该警告的程序语句出现错误的可能性。为了提高编程质量,最好把编译警告调节到Level 4,同时认真审查每一个警告信息。为了确定出现某个警告的程序语句出错的可能性,你可以通过对警告信息进行理解,也可以将警告代号输入MSDN查看其级别,如果是1,2,3级,那么问题通常较严重,这时必须找出代码的问题,然后进行修改。再举一例:

void f(int a,int b)

{

         Int c;

         c==0;                // warning C4553: '==' : operator has no effect; did you intend '='?

         if (a=b) { }        // warning C4706: assignment within conditional expression

}

该程序段中的两个警告都与“=”或者“==”有关。C4553属于L1级别的警告,因为这样的语句毫无意义(将c于0做比较运行得到一个bool值,然后丢弃),所以它通常意味着程序员“手误”,所以其警告级别较高。

C4706是L4级别的警告,由于有时候在if语句的调价表达式中使用“=”和“==”都是有意义的,而程序员又习惯性将二者弄错,因此编译器就用警告信息来提醒程序员确认是否弄错。如果程序员的本意就是使用“=”的话,那么可以通过将条件表达式写成if((a=b)!=0)来避免产生警告信息。为了尽量避免程序员将“==”误写成“=”,当参与比较的两个数中有常量时,通常可以将常量写在左边,如将if(x==2)写成if(2==x),这样如果错误地将“==”写成了“=”就会出现编译错误:error C2106: '=' : left operand must be l-value

编译警告数量庞大,要一一举例即使不是不可能的也是非常困难的。这里不再赘述,通常我们需要把平时编程过程中遇到的问题记录在案,并时刻温习,这样就可以降低日后编程中的出错率。本篇分析C4700,C4100,C4101,C4553,C4706等常见警告的其目有三个:首先是弄懂什么情况下会产生某个警告;其次是深刻理解某个警告所预示的程序质量问题;最后是得出一个结论:为了不放过任何有可能影响程序质量的警告,请使用最高编译警告级别。

3. # pragma warning的用法

3.1.    #pragma warning( warning-specifier : warning-number-list [,warning-specifier : warning-number-list...] )

其中warning-specifier包含:once,default,disable,error,而warning-number-list就是警告的编号,如前面提到的C4700。该用法的作用是:只让某些警告只出现一次(once)或者使用默认警告级别(default),或者禁用某个警告(disable),或者将警告视为错误(error)。如#pragma warning( disable : 4507 34; once : 4385; error : 164 ;default:4705)与下面三句的作用一样

#pragma warning( disable : 4507 34 ) // Disable warning messages 4507 and 34.

#pragma warning( once : 4385 )        // Issue warning 4385 only once.

#pragma warning( error : 164 )        // Report warning 164 as an error.

#pragma warning( default : 4705 )  //use default warning to 4705

注意:当警告编号大于4699时,上述语句只有写在包含出现警告的那个语句的函数外部才有效。比如要把如下的函数中的C4706警告禁止(当做错误处理、按默认处理)那么只有把#pragma warning(disable,4706)放到函数体外面才有效:

#pragma warning(disable,4706)               //有效

void f(int a,int b)

{

                                     #pragma warning(disable,4706) //无效

                                     if (a=b)      {        }                 //warning c4796

}

 

3.2.    #pragma warning( push[ , n ] )和#pragma warning( pop)

二者通常配合使用,前者是指定编译警告级别为1,2,3,4级,后者是恢复之前的编译警告级别。例如你的某段程序在Level 4下“不干净”,那么你可以在该程序段前使用#pragma warning( push,3 )并在该程序段后使用#pragma warning( pop )来避免不干净的程序曝光,同时也不影响其他人写的程序部分的编译警告级别。

4. 处理警告信息

针对每一个警告都要仔细审查,并选择合适的处理方式:如果是“无关紧要之警告”,可以置之不理,当然如果你觉得每次编译都弹出一些警告让人看着心烦,那么你可以在出现该“无关紧要之警告”之处将该警告disable掉,然后再将该警告打开;如果该警告指示的语句确实存在出错或者已经出错,那么该警告就是“紧要之警告”,对待这类警告只有一个办法:修改程序!将编译器的警告级别调至最高级的目的就是发现更多的“紧要之警告”当然这也会让程序员受到更多的“无关紧要之警告”的烦扰,为了提高程序的质量,受点小小烦扰还是值得的。

那么如何减少程序编译时的警告信息呢?最简单的办法是使用将警告级别设置为None(/W 0),这时什么警告都没了,呵呵。我相信您会说这是自欺欺人?是的,确实如此,程序员将所有警告都无视掉的后果就是这些警告将偷偷地报复您——出现很多很难发现的运行期错误或者逻辑错误!但是,将某些确定不会发生错误的警告disable掉是可以的。除此之外,最重要的是养成良好的编程习惯,比如说定义变量的同时就初始化,使用指针之前进行指针合法性检查等(这些优良的编程习惯可以在很多书籍中学到,如《编程精粹》、《高质量C++程序设计指南》、《More Effective C++》、《C++编程思想》……)。如果您觉一时半会学不会那些优良的编程习惯,那么最容易学会,也最容易使用的一个编程习惯就是:设置编译警告为最高级别(Level 4)。

5. 后话

作者最近在阅读《编程精粹——Microsoft编写优质无错程序秘诀》,目前还只完成了前面20页的阅读,而本篇就是作者在读该书时的感悟。本篇没有任何新颖的知识(绝大部分内容来自于MSDN Library),但是作者仍然觉得必要记下这篇读书笔记。一来是怕自己忘记这些知识,而来是可以和朋友们进行分享。

由于作者水平限制,如有不妥之处还望各位朋友指正,如有遗漏之处还望热心的朋友补充!

参考文献

1、《编程精粹——Microsoft编写优质无错程序秘诀》

2、MSDN Library – October 2001

3、《高质量C++程序设计指南》

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多