2024年6月11日发(作者:)
PC-lint研究总结
PC-lint总体介绍 ....................................................................................................................... 2
安装和配置 ............................................................................................................................... 2
2.1 安装 ........................................................................................................................... 2
2.2 配置 ........................................................................................................................... 3
使用方法 ................................................................................................................................. 14
3.1 命令行方式 ............................................................................................................. 14
3.2 集成到IDE方式 ...................................................................................................... 15
3.2.1 集成到VC中 ........................................................................................... 15
3.2.2 集成到CB中 ........................................................................................... 17
3.2.3 集成到SI中 ............................................................................................. 20
3.2.4 集成到UE中 ........................................................................................... 23
3.3 makefile方式 ........................................................................................................... 24
3.3.1 GNU Make和makefile介绍 .................................................................... 24
3.3.1.1 GNU Make .............................................................................................. 24
3.3.1.2 makefile 基本结构 ................................................................................. 25
3.3.1.3 makefile 变量 ......................................................................................... 26
3.3.1.4 GNU make 的主要预定义变量 ............................................................. 26
3.3.1.5 隐含规则 ................................................................................................. 27
3.3.2 平台的makefile结构 ............................................................................... 27
3.3.2.1 平台级 ..................................................................................................... 28
3.3.2.2 子系统级(以支撑为例) ..................................................................... 28
3.3.2.3 平台makefile的调用方式 ....................................................................... 29
3.3.3 平台makefile同PC-lint的集成 ............................................................... 29
平台推广方案(建议) ......................................................................................................... 34
4.1 推广使用的前提 ..................................................................................................... 34
4.2 个人的使用方案 ..................................................................................................... 34
4.3 子系统的检查人的使用方案 ................................................................................. 34
4.4 特殊情况处理方法 ................................................................................................. 34
1.
2.
3.
4.
1. PC-lint总体介绍
PC-Lint/FlexeLint for C/C++是GIMPEL SOFTWARE公司的产品,是C/C++软件代码静
态分析工具,你可以把它看作是一种更加严格的编译器。它除了可以检查出一般的语法错误
外,还可以检查出那些虽然符合语法要求,但很可能是潜在的、不易发现的错误。
C语言的灵活性带来了代码效率的提升,但相应带来了代码编写的随意性,另外C编
译器不进行强制类型检查,也带来了代码编写的隐患。PC-Lint识别并报告C语言中的编程
陷阱和格式缺陷的发生。它进行程序的全局分析,能识别没有被适当检验的数组下标,报告
未被初始化的变量,警告使用空指针,冗余的代码,等等。软件除错是软件项目开发成本和
延误的主要因素。PC-lint能够帮你在程序动态测试之前发现编码错误。这样消除错误的成
本更低。
PC-lint全球拥有广泛的客户群,因为它性价比高,易于学习,容易推广和固化到软件
开发测试流程中去。使用方法很简单,可以用命令行方式进行,例如lint-nt –u test1.c
test2.c test3.c 。 另外支持MAKEFILE方式。也可以集成到开发环境中。如集成到Source
Insight/SLICKEDIT/MS VC6.0/KEIL C..等。
如微软公司,都把它作为程序检查工具,在程序合入正试版本或交付测试之前一定要
保证通过了LINT检查,他们要求软件工程师在使用LINT时要打开所有的编译开关,如果一
定要关闭某些开关,那么要给出关闭这些开关的正当理由。
由于越来越多的用户要求能在非PC的平台上使用PC-Lint,GIMPEL公司采用了标准C
源码包的方式发布了FlexeLint,这样一来,FlexeLint就可被用户方便的移植在各种的平台
上。
2. 安装和配置
2.1 安装
由于此版本比较特殊,故无需安装,只需要将RAR压缩包里的所有文件解压到一个目
录下即可,这里假定为C:PCLint8目录。
解压之后目录中文件及目录的说明如下:
PC-lint可执行程序;
配置向导;
手册的补充说明,最新特性的更新都在这里说明;
PC-Lint英文参考手册,许多问题的答案可以在这里找到;
打印工具;
文本格式的错误号信息解释文件;
LINT补丁升级工具;
Lnt 包含各种lnt配置文件的目录,文件如下:
co-....lnt 各种特定的编译器的配置文件;
通用的编译器配置文件;
sl-....c 支持各种非ANSI标准编译器的标准库模块;
sl.c 支持ANSI标准的通用编译器的标准库模块;
lnt 支持包括微软Visual Studio在内的各种开发环境以及多种编辑工具的配
置文件;
lnt 支持各种特定库的配置选项文件;
au-....lnt 作者编程建议的配置文件;
Test. 包含各种测试源文件的目录。
2.2 配置
假定我们为Microsoft Visual C++ 6的开发环境进行配置,运行C:进
行配置。
1) 运行C:后出现如下画面,选择下一步;
图 2.2.1
2) 出现命令行使用的说明窗口,选择下一步;
图 2.2.2
说明:,,为成功运行完此配置向导后自动生成的文
件。LINT-NT实际上就是命令。中的命令语句形式还可在IDE
或编辑器里使用;
3) 选择是创建或修改已有配置文件的选项,这里我们是第一次配置,故选
择上面一个选项Create a new ,不修改配置路径,然后选择下一步;
图 2.2.3
说明:界面中配置路径不修改的话就是PC-Lint安装的路径C:PCLint8,新建的
就存放在这个目录下,当然用户也可选择另外的配置路径存放生成的。
4) 接下来是选择编译器,在下拉框中选择自己使用的编译器。这里我们选择Microsoft
Visual C++ 6.x (),点击下一步;
图 2.2.4
说明:如果没有自己使用的编译器,可选择通用编译器:Generic Compilers。这个选项
会体现在文件中,并存放在前面我们选择的配置路径(C:PCLint8)下,在
后面配置选项我们所选择的***.LNT均会被存放到这个路径下。
5) 接着会让你选择一个的内存模型,可以根据自己程序区和数据区的实际大小选择一
个恰当的内存模型。内存模型的选项会体现在文件中。缺省选择32-bit
Flat Model.,然后选下一步;
图 2.2.5
6) 选完内存模型后,会看到一个库类型的列表,在这里选择一个或多个编译时使用的
库。建议选择Microsoft's Foundation Class library 和Windows NT,Windows 32-bit,
如果你用到了Standard Template Library,当然也选上,选择下一步;
图 2.2.6
说明:各种库的配置文件名为,配置向导会把选中的库的lnt配置文件拷贝到
配置路径下。
7) 接着是让你选择为使用C/C++编程提出过重要建议的作者,选择的某作者后,他提
出的编程建议方面的选项将被打开,作者建议的配置名为。建议全部
选择,选择下一步;
图 2.2.7
说明:同样,选中作者建议的,也会被配置向导拷贝到配置路径下。
8) 下一步是选择用何种方式设置包含文件目录。这里我们选择用-i方式协助我们来设
置,然后选择下一步;
图 2.2.8
说明:这里有两种选项:第一种选项是使用-i选项协助我们设置,-i选项体现在
文件中,每个目录前以-i引导,目录间以空格分隔。第二种是跳过这一步,手工设置。
建议选择第一种。
9) 如果步骤8中选择使用-i选项,安装程序会接着让你输入包含文件所在的目录。在
下面的文本框里,可手工输入文件包含路径,用分号“;”或用ctrl+Enter换行来分
割多个包含路径。或者可以点中Brows,在目录树中直接选择。填完后选择下一步;
图 2.2.9
说明:如果不输入包含文件目录,直接选择下一步,在安装完成后在文件中手工
添加,注意如果目录名中有长文件名,使用时要加上双引号””,如-i”E:Program
FilesMSVCVC98Indlue”。
10) 然后出现以下对话框,表示std_,在配置路径下已被创建,这里的
实际上就是std_的一个拷贝,只是在缺省方式下,lint时使用的配置文件是
;
图 2.2.10
选择确定后,这里将会问你是否进行另一个编译环境的配置。这里我们选择否。
图 2.2.11
说明:如果选是,将会从第4步开始进行配置,在配置完了包含路径后,会出现下面的
对话框,表示另一个编译环境的std_配置文件在配置路径下被创建,并且问你是否
要用这个文件替换已经存在的,以使得最后配置的编译环境的配置成为lint时的
缺省配置。
图 2.2.12
11) 接下来将会准备产生一个控制全局编译信息显示情况的选项文件,
这里选择No,即不取消这些选项
。
图 2.2.13
说明:该文件的产生方式有两种,一种是安装程序对几个核心选项逐一解释并提问你是
否取消该选项,如果你选择取消,则会体现在文件中,具体体现方式是
在该类信息编码前加-e,后面有一系列逐一选择核心选项的过程。如果选择第二种选择
方式,安装文件会先生成一个空的文件,等你以后在实际应用时加入必
要的选项。
12) 接着选择所支持的集成开发环境选项,可选多个或一个也不选,PC-LINT提供了
集成在多种开发环境中工作的功能,例如可集成在VC、BC、Source Insight中。这
里我们选择MS VC++6,这样就会被拷贝到配置路径中。
图 2.2.14
13) 安装程序会生成一个文件,该文件是运行PC-LINT的批处理文件,为了
使该文件能在任何路径下运行,安装程序提供了两种方法供你选择。第一种方法是
让你选择把拷贝到任何一个PATH目录下。第二种方法是生成一个
文件,在每次使用PC-LINT前先运行它来设置路径,或者把
文件的内容拷贝到文件中。建议选择第一种方法,指定的目录为
安装目录。
图 2.2.15
图 2.2.16
14) 配置完毕。
图 2.2.17
说明:以上配置过程中在配置路径下产生的多个*.lnt文件,除了,std_,
std_,为配置向导所生成,其它,,均是从
C:Lint8lnt中拷贝出来的,在这个目录下还有其它PCLint所支持的编译器、库及集成开发
环境的lnt配置文件,所有的lnt文件均为文本文件。
上面的配置方法适合于刚开始接触PC-lint时使用,对于熟练的使用者可以直接编辑、
编写各*.lnt配置文件安成上面的配置工作,或者定制出更适合自己使用的配置环境。
3. 使用方法
3.1 命令行方式
命令行的使用方式是PC-lint最基本的使用方式,也是其他各种集成使用方式的基础,
通过命令行可以完成PC-lint的全部代码分析工作。
PC-lint的命令行有下列形式:
Lint-nt option file1 [file1 file3 …]
其中的Lint-nt是PC-lint的可执行程序(见2.1安装),它完成PC-lint的基本功能;option
代表PC-lint可接受的各种选项,这是PC-lint最为复杂的部分,它的选项有300多种,可以
分为:错误信息禁止选项、变量类型大小选项、冗余信息选项、标志选项、输出格式选项和
其他选项等几类,后面会有更多的介绍;file为待检查的源文件。
另外值得注意的一点是,在命令行中可以加入前面提到的*.lnt配置文件名,并可以把
它看作是命令行的扩展,其中配置的各种选项和文件列表,就和写在命令行中具有一样的效
果。
3.2 集成到IDE方式
3.2.1 集成到VC中
在集成开发环境中,PC-Lint 8.0对VC++6和VC++7.0的支持是最完善的,支持直接从
VC的工程文件(VC6是*.dsp,VC7是*.vcproj)导出对应工程的.Lnt文件,此文件包含了
工程设置中的预编译宏,头文件包含路径,源文件名,无需人工编写工程的.Lnt文件。
下面是集成到的VC6中的tools设置说明,参见C:中的注释:
导出当前工程的.lnt文件(用来导出工程设置和源文件名,头文件包含路径),下面的对
话框点击菜单的Tools->Customize->Tools可以看到。
图 3.2.1.1
PC_LINT 8.0 Export
Command: C:
Arguments: +linebuf $(TargetName).dsp>$(TargetName).lnt
Initial directory: $(TargetDir)..
当修改过工程设置中的头文件包含路径、预编译宏或新增源文件后,需要重新导出工
程的lnt文件,否则修改后的设置无法自动体现在工程的lnt文件中。
以上设置只要修改Command中的所在路径即可。执行过这个命令后,
$(TargetName).lnt被放到$(TargetName).dsp所在目录中($(TargetDir)..下),如果成功,打印
出的返回值为0,如果失败则返回非零值,具体出错信息需查看$(TargetDir)..
$(TargetName).lnt文件内容。
其中$(…)的字串为VC的参数宏,调用工具命令时VC将它们替换为对应的字符串。
$(TargetName)为当前激活的工程名(通过菜单Project->Set Active Project设置当前激活工程,
或在WorkSpace的工程树上右键对应的工程选择Set as Active Projec),$( TargetDir)为当前
激活工程输出目标文件所在路径(一般缺省为工程所在目录下的Debug或Release目录),
具体参数宏的含义说明参考MSDN中的VC的使用指南。
在Initial directory 的$(TargetDir)..表示在这个目录下执行此命令。
注:参数+linebuf表示
加倍行缓冲的大小,最初是600 bytes。行缓冲用于存放当前
行和你读到的最长行的信息。
检查当前激活工程中当前窗口中的源文件。
图 3.2.1.2
PC_LINT 8.0 For Unit Check
Command: C:
Arguments: -i"C:PCLint8" -u $(TargetName).lnt "$(FilePath)"
Initial directory: $(TargetDir)..
注意这一步最容易出错误。。 与。,路径不同,会出现找不到头文件的路径的情况
执行此命令前提是$(TargetDir)..目录下已经有工程的$(TargetName).lnt文件,这个文件
在步骤1中生成。
第一个参数-i"C:PCLint8"为lint搜索*.lnt文件的目录,这里就是我们的配置路径。
就是前面配置过程中生成编译环境的配置文件,如果有需要支持多个编译环境,
可以直接改成对应的配置文件名,例如这里可以改为std_,表示使用std_中所配置
的编译环境设置。
最后一个参数"$(FilePath)"就是当前窗口中的带路径的源文件名。需要注意的是,当前
窗口中打开的源文件一定要属于当前激活的工程,否则lint可能会出错。
检查当前激活工程中的所有源文件。
图 3.2.1.3
PC_LINT 8.0 For Project Check
Command: C:
Arguments: +ffn -i"C:PCLint8" $(TargetName).lnt
Initial directory: $(TargetDir)..
执行此命令前提是$(TargetDir)..目录下已经有工程的$(TargetName).lnt文件,这个文件
在步骤1中生成。
此命令把$(TargetName).lnt中所包含的源文件lint一遍,如果工程比较大的话,一般输
出的内容会超过VC的输出窗口的缓冲区大小,导致只能看到后面一部分Lint的信息,可
以把Arguments改为
+ffn -i"C:PCLint8" $(TargetName).lnt>$(TargetName).txt
把结果输出到一个名为$(TargetName).txt的文件里。
注:参数中的+ffn
表示Full File Names,可被用于控制是否使用的完整路径名称
表示。
3.2.2 集成到CB中
从PC-Lint8.0j版本开始,支持从C++Builde 6的工程文件.bpr导出工程的Lint配置。
自动导出C++ Builder6的工程设置lnt文件的方法参见C:中的说明,下
面是集成到的CBuilder6中的tools设置说明:
运行C:将lint环境配置为C++Builder的。
要求需要lint的工程的工程文件.bpr,源文件.c、.cpp和最后工程生成的最终文件这3
者要在同一个目录下,否则下面说明中的Working dir要手工修改。
导出当前工程的.lnt文件(用来导出工程设置和源文件名,头文件包含路径):
从C++ Builder的Tools菜单中选择“Configure Tools”命令项,在打开的Tool Options对
话框中点击Add按钮。按
图 填入各项参数。由于CBuilder6对命令行参数传递重定向命令的处理有问题,所以
还需要编写一个完成此功能。
图 中各参数的内容参见后面的批处理文件内容的注释。
图 3.2.2.1
的内容如下:
@echo off
rem Title: &Export file
rem Program: E:
rem Parameters: $NAMEONLY($EXENAME).bpr $NAMEONLY($EXENAME).lnt
@echo on
C: +fpa -d"BCB=e:borlandcbuilder6" %1>%2
其中E:BorlandCBuilder6为C++Builder6的安装目录,需根据实际情况修改。
$NAMEONLY()是CBuilder6提供的一个宏函数,将文件名的后缀去掉,$EXENAME
代表了工程最后生成的文件名,这里要求它和工程的.bpr去掉后缀的文件名同名。
检查当前工程中当前窗口中的源文件:
图 3.2.2.2
Title: Lint For Current File
Program: C:
Parameters: $SAVE -u -v -iC:Lint8 std env-cb $NAMEONLY($EXENAME).lnt
$EDNAME
和VC6的集成方法一样,由于参数中需要工程的lnt文件,执行此命令前提是源文件所
在目录下已经有工程的$NAMEONLY($EXENAME).lnt文件,这个文件在步骤3中生成。
其中$EDNAME表示当前编辑的源文件名。
检查当前工程中的所有源文件。
图 3.2.2.3
Title: &Lint For Current Project
Program: C:
Parameters: $SAVEALL -v -i"C:Lint8" std env-cb $NAMEONLY($EXENAME).lnt
执行此命令前提是源文件所在目录下已经有工程的$NAMEONLY($EXENAME).lnt文
件,这个文件在步骤3中生成。
设置Build Tool在Project Manager中检查指定的单个源文件:
从C++ Builder的Tools菜单中选择“Build Tools”命令项,在打开的Build Tools对话框中
点击Add按钮。按
图 填入各项参数。
图 3.2.2.4
Title: Lint
Other Extensions: .cpp;.c;.cxx
Command Line: C: -u
-i$INCLUDEPATH -D$DEFINE $NAME $SAVE
-i"C:Lint8"
其中$INCLUDEPATH为工程设置中的include路径,$DEFINE为工程设置的预编译宏。
添加后,如
图在Project Manager中的源文件上点击鼠标右键,选择Lint就开始检查选中的文件了,
输出信息在Build窗口。
图 3.2.2.5
3.2.3 集成到SI中
Source Insight的集成方法参见C:中的注释。
从Options菜单中选择“Custom Commands”命令项。点击Add…。
在Name栏中输入“PC-lint unit check”,原则上这个名称可以随便起,只要你能搞清楚
它的含义就可以了。
在Run栏中输入“C:Lint8lint-nt -u -iC:Lint8 std env-si %f”其中C:Lint8是你PC-LINT
的安装目录。
在Output栏中选择“Iconic Window”、“Capture Output”。
在Control栏中选择“Save Files First”。
在Source Links in Output栏中选择“Parse Links in Output”、“File,then Line”。
在Pattern栏中输入“^([^ ]*) ([0-9]+)”。
点Close键加入该命令。如下图:
图 3.2.3.1
使用时,在Source Insight下打开要LINT的文件,打开Options菜单中的
“Custom Commands”命令项,在“Command”栏中选择“PC-lint unit check”命令运行即
可。请注意,不论你怎样配置参数一定不要忘记了将包含在你的配置文件里,否
则就无法进行错误信息和程序的自动对应了。
用Menu命令把PC_Lint添加到菜单中。
图 3.2.3.2
至此,你可以运行source insight下集成的PC-Lint功能完成对代码的走查,并且很
方便的找到错误信息的位置。
图 3.2.3.3
在错误信息处点击旁边的红色图标,就会自己跳转至错误出现处。上图就是一个不安
全变量转换的警告信息。
以上是对在source insight3.5中使用集成PC-lint的一个总结。我们可以看到该方式使用
PC-lint简单易用,容易查找到错误,但考虑只能做当前文件的单元检查,需要自己指定
Include目录和需要自己定义相关的宏等,设置过程比较麻烦,并且不够通用。此方式适合
个人维护自己的代码用,对于整个部门的代码的走查,还是采取makefile的方式比较好。
3.2.4 集成到UE中
图 3.2.4.1
(1)从UltraEdit的<高级>菜单中进入<工具配置>
(2)<菜单项目名>栏输入“PC-lint unit check”
(3)<命令行>栏输入以下命令:C:PCLint8LINT-NT –u -iC:PCLint8si std env-si %f
其中,C:PCLint8是PC-Lint的安装目录
(4)<工作目录>栏输入以下路径:x:code
(5)选中<先保存所有文件>的复选框
(6)在<命令输出>栏中,选中<输出到列表>和<捕捉输出>
(7)点<插入>将命令行插入UltraEdit的菜单,此时在UltraEdit的<高级>菜单中会增
加一个
结果如下图所示:
图 3.2.4.2
3.3 makefile方式
这里makefile指的是一类文件,用在C/C++的Make工具中,Make工具通过makefile
文件来描述源程序之间的相互关系,并自动维护编译工作。Makefile文件按照特定的语
法进行编写,文件中需要说明如何编译各个源文件,并连接生成可执行文件。
Makefile文件作为一种描述文档一般需要包含以下内容:
◆ 宏定义
◆ 源文件之间的相互依赖关系
◆ 可执行的命令
Makefile中允许使用简单的宏指代源文件及其相关编译信息,也称宏为变量。在引
用宏时只需在变量前加$符号,但值得注意的是,如果变量名的长度超过一个字符,在
引用时就必须加圆括号()。
平台目前使用的Make工具是Tornado集成环境中的自带的(一般位于Tornado
安装目录hostx86-win32bin),它实际上是GNU Make version 3.74 (vpath+),以上的版
本信息具体情况可能会略有不同,可以通过make –v来查看。那么,在说明平台的
makefile结构和如何将PC-lint结合到makefile中使用之前,先来介绍GNU Make及
makefile。
3.3.1 GNU Make和makefile介绍
3.3.1.1 GNU Make
在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令
进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工
作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果
某个头文件被修改了,则重新编译所有包含该头文件的源文件。利用这种自动编译可
大大简化开发工作,避免不必要的重新编译。
实际上,make 工具通过一个称为 makefile 的文件来完成并自动维护编译工作。
makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可
执行文件,并定义了源文件之间的依赖关系。
当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依
赖该文件的源文件。
makefile 文件是许多编译器,包括 Windows NT 下的编译器维护编译信息的常用方法,
只是在集成开发环境中,用户通过友好的界面修改 makefile 文件而已。
默认情况下,GNU make 工具在当前工作目录中按如下顺序搜索 makefile:
* GNUmakefile
* makefile
* Makefile
在 UNIX 系统中,习惯使用 Makefile 作为 makfile 文件。如果要使用其他文件作为
makefile,则可利用类
似下面的 make 命令选项指定 makefile 文件:
$ make -f
3.3.1.2 makefile
基本结构
makefile 中一般包含如下内容:
* 需要由 make 工具创建的项目,通常是目标文件和可执行文件。通常使用“目标
(target)”一词来表示要创建的项目。
* 要创建的项目依赖于哪些文件。
* 创建每个项目时需要运行的命令。
例如,假设你现在有一个 C/C++ 源文件 test.C,该源文件包含有自定义的头文件
test.h,则目标文件 test.o 明确依赖于两个源文件:test.C 和 test.h。另外,你可能只希
望利用 g++ 命令来生成 test.o 目标文件。这时,就可以利用如下的 makefile 来定义
test.o 的创建规则:
# This makefile just is a example.
# The following lines indicate how test.o depends
# test.C and test.h, and how to create test.o
test.o: test.C test.h
g++ -c -g test.C
从上面的例子注意到,第一个字符为 # 的行为注释行。第一个非注释行指定 test.o 为
目标,并且依赖于 test.C 和 test.h 文件。随后的行指定了如何从目标所依赖的文件建
立目标。
当 test.C 或 test.h 文件在编译之后又被修改,则 make 工具可自动重新编译 test.o,
如果在前后两次编译之间,test.C 和 test.h 均没有被修改,而且 test.o 还存在的话,
就没有必要重新编译。这种依赖关系在多源文件的程序编译中尤其重要。通过这种依
赖关系的定义,make 工具可避免许多不必要的编译工作。当然,利用 Shell 脚本也可
以达到自动编译的效果,但是,Shell 脚本将全部编译任何源文件,包括哪些不必要重
新编译的源文件,而 make 工具则可根据目标上一次编译的时间和目标所依赖的源文
件的更新时间而自动判断应当编译哪个源文件。
一个 makefile 文件中可定义多个目标,利用 make target 命令可指定要编译的目标,
如果不指定目标,则使用第一个目标。通常,makefile 中定义有 clean 目标,可用来
清除编译过程中的中间文件,例如:
clean:
rm -f *.o
运行 make clean 时,将执行 rm -f *.o 命令,最终删除所有编译过程中产生的所有中
间文件。
3.3.1.3 makefile
变量
GNU 的 make 工具除提供有建立目标的基本功能之外,还有许多便于表达依赖性关系
以及建立目标的命令的特色。其中之一就是变量或宏的定义能力。如果你要以相同的
编译选项同时编译十几个 C 源文件,而为每个目标的编译指定冗长的编译选项的话,
将是非常乏味的。但利用简单的变量定义,可避免这种乏味的工作:
# Define macros for name of compiler
CC = gcc
# Define a macr o for the CC flags
CCFLAGS = -D_DEBUG -g -m486
# A rule for building a object file
test.o: test.c test.h
$(CC) -c $(CCFLAGS) test.c
在上面的例子中,CC 和 CCFLAGS 就是 make 的变量。GNU make 通常称之为变量,
而其他 UNIX 的 make 工具称之为宏,实际是同一个东西。在 makefile 中引用变量
的值时,只需变量名之前添加 $ 符号,如上面的 $(CC) 和 $(CCFLAGS)。
3.3.1.4 GNU make
的主要预定义变量
GNU make 有许多预定义的变量,这些变量具有特殊的含义,可在规则中使用。表 1-5
给出了一些主要的预定义变量,除这些变量外,GNU make 还将所有的环境变量作为
自己的预定义变量。
表 1-5 GNU make 的主要预定义变量
预定义变量 含义
$* 不包含扩展名的目标文件名称。
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重
复的依赖文件。
$< 第一个依赖文件的名称。
$? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创
建日期晚。
$@ 目标的完整名称。
$^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。
$% 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,
如果目标名称为 (image.o),则 $@ 为 ,而 $% 为 image.o。
AR 归档维护程序的名称,默认值为 ar。
ARFLAGS 归档维护程序的选项。
AS 汇编程序的名称,默认值为 as。
ASFLAGS 汇编程序的选项。
CC C 编译器的名称,默认值为 cc。
CFLAGS C 编译器的选项。
CPP C 预编译器的名称,默认值为 $(CC) -E。
CPPFLAGS C 预编译的选项。
CXX C++ 编译器的名称,默认值为 g++。
CXXFLAGS C++ 编译器的选项。
FC FORTRAN 编译器的名称,默认值为 f77。
FFLAGS FORTRAN 编译器的选项。
3.3.1.5
隐含规则
GNU make 包含有一些内置的或隐含的规则,这些规则定义了如何从不同的依赖文件
建立特定类型的目标。
GNU make 支持两种类型的隐含规则:
* 后缀规则(Suffix Rule)。后缀规则是定义隐含规则的老风格方法。后缀规则定义了
将一个具有某个后缀的文件(例如,.c 文件)转换为具有另外一种后缀的文件(例如,.o
文件)的方法。每个后缀规则以两个成对出现的后缀名定义,例如,将 .c 文件转换为 .o
文件的后缀规则可定义为:
.c.o:
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
* 模式规则(pattern rules)。这种规则更加通用,因为可以利用模式规则定义更加复杂
的依赖性规则。模式规则看起来非常类似于正则规则,但在目标名称的前面多了一个 %
号,同时可用来定义目标和依赖文件之间的关系,例如下面的模式规则定义了如何将
任意一个 X.c 文件转换为 X.o 文件:
%.c:%.o
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
3.3.2 平台的makefile结构
平台的makefile文件也称为工程文件,主要由名为makefile和*.mak的两类文件构成。
平台的makefile文件可以分为两个等级:平台级和子系统级。
平台级的makefile是生成最终的执行代码的工程文件,它还需要调用源程序体系中的子
系统级的工程文件(或更深级别的工程文件)来最终完成自己的工作,它一般位于一级目录
中的PROJECT目录。
子系统级的makefile,主要是根据不同的编译选项来生成各种类型的目标文件(例如、
各种逻辑单板的DEBUG、RELEASE版),它一般分布在一级目录CODE的各子系统、模
块的目录中。它所需的各种选项由平台级的工程文件传递下来。
PROJECT目录的组织规则是按照网元、物理单板和逻辑单板三层组织结构进行组织的。
对于网元级目录,目前包括:BSC、PCF、AGW、HA、RNC、WCN,分别用来存放BSC、
PCF、AGW、HA、RNC、WCDMA CN的版本。
对于物理单板级,没有工程文件,在其下按照该物理单板支持的逻辑单板创建相应的逻
辑单板的目录,对于一种物理单板,可能有多种逻辑单板,或者多种逻辑单板的合一版本。
在本目录中,存放所有的逻辑单板以及一些典型的合一单板的目录。
对于逻辑单板级,存放的是相应逻辑单板的工程文件。
3.3.2.1 平台级
在Project目录下,有下列文件。
:计算各种CPU的编译参数,包括字节序和编译器的路径等。
:输入参数的合法性检查。
:配置各种CPU的编译选项。
:用于配置编译平台或者事业部的选项。
:用于有多个CPU时对每个CPU连接可执行文件。
:用于连接特定序号的CPU的可执行文件。
Makefile:顶级makefile,被调用,用于编译所有的网元的所有单板。
:编译网元的批处理文件。
:用于支持多种CPU子卡的编译
:被makefile调用,用于编译一个指定网元的所有单板。
:用于编译指定的子系统
:调用完成对一块单板的编译,并提供对单个子系统进
行编译时的入口。
:平台的配置文件,其中配置了平台支持的物理板、逻辑板、CPU等。
:链接文件。
:调用相关子系统的makefile,以便进行编译。
:完成对Tornado路径的自动切换,并且调用开始编译。
:用于配置版本的信息,包括版本的路径以及版本号等。
3.3.2.2 子系统级(以支撑为例)
在./code/oss目录下,有下列文件。
——支撑子系统的配置文件。
——支撑子系统的顶级makefile。
对于支撑的各个子模块,各有一个以后缀名为.mak的文件,负责自身文件的编译过程。
一共包含下面几个文件:
./code/pub/——平台级公用部分。
./code/oss/Common/——子撑子系统对内的公用部分。
./code/oss/File/——文件子模块
./code/oss/Device/——设备子模块。
./code/oss/Excep/——异常子模块。
./code/oss/Comm/——通信子模块。
./code/oss/Mem/——内存子模块
./code/oss/Pub/——支撑子系统对外的公用部分。
./code/oss/Sche/——调度子模块。
./code/oss/SDL/——SDL子模块。
./code/oss/Timer/——定时器子模块。
./code/oss/VOS/——Vos子模块。
3.3.2.3
平台
makefile
的调用方式
这里以UPCF单板、OSS子系统、COMMON子模块为例进行说明。
假设我们要编译UPCF单板的OSS子系统,最终生成名为Oss_SubsysEx.o的目标文件。
程序目录已被映射为X:盘。Tornado for Arm已安装在D:TornadoArm目录下。
1. 进入X:Project目录,调用参数为arm设置相应的环境变量;
2. 进入X:projectPCFmnicupcf目录,调用参数为Oss开始编译;
3. make自动搜索到当前目录下的makefile文件,开始执行编译;
4. makefile除设置版本、CPU、单板等变量外,包含进../../等
文件;
5. 除设置网元变量外,包含进
$(_PROJECT_PATH)/文件;
6. 计算CPU的数量,并通过命令的方式执行
$(_PROJECT_PATH)/;
7. 根据CPU的类型调用并把所需的参数传递给
该批处理;
8. 执行
call make -C %make_project_path% -f %make_subsystem%调入并
把子系统参数Oss传递给它;
9. 根据参数Oss执行相应的部分
@$(MAKE) -C $(_SOURCE_PATH)/oss -f 调用程序级的;
10. 包含进OSS系统各子模块的makefile,其中包括COMMON子模块的
;
11. 生成本模块的目标文件;
12. 把各模块的目标文件连接成子系统的目标文件,完成编译;
上面的过程中,只列出了关键步骤的主线makefile,并没有写出只作了变量设置的
makefile,是为了便于描述,不致显得零乱。
3.3.3 平台makefile同PC-lint的集成
通过上节的执行过程可以看出,从第9步开始makefile由平台级转到了子系统级,真
正的编译工作也是从第9步开始的,根据传入的参数Oss去执行相应部分,完成具
体的功能。
那么,首先我们来看一下具体结构:
Default: BaseFunc
…
BaseFunc: MakeForce
…
CleanLinkFile:MakeForce
…
MakeForce:
## BSP子系统
Bsp:BaseFunc BspTarget
BspTarget:
…
BspClean:
…
BspCleanAll:
…
## OSS子系统
Oss:BaseFunc
@echo 开始支撑子系统的编译
$(_MAK_CONTINUE_WHEN_ERROR) @$(MAKE) -C $(_SOURCE_PATH)/oss -f
OssOnly
OssTarget:
…
OssClean:
…
OssCleanAll:
…
## SCS 子系统
…
## DBS子系统
…
## SIG子系统
…
## BRS子系统
…
## MCS子系统
…
## OAM子系统
…
## PP子系统
…
## TEST子系统
…
## DivisionAppTarget
…
实际上,在我们上面的例子中,当执行make Oss之后,在里只是执行的Oss:
那一段的带有下划线的代码,完成OSS子系统的编译。为每一个子系统都定义了
若干个目标,还是以OSS子系统为例,就有:Oss、OssTarget、OssClean、OssCleanAll这几
个目标,分别完成子系统的编译和生成文件的清理,其他的各个子系统都定义了同样类似的
目标。
前面我们讲了这么多,有makefile的原理,有平台目前makefile的结构,其实最终的
目的都是为了看如何把PC-lint同makefile结合起来,完成系统代码批量检查的功能。那么,
就是关键所在,我们可以在里为每个子系统增加一个目标定义来完成调用
PC-lint进行静态代码分析。比如:可以为OSS子系统增加一个目标OssLint。
下面将列出为增加OssLint对平台的makefile所作的修改:
OssLint:
@echo 开始支撑子系统的Lint
$(_MAK_CONTINUE_WHEN_ERROR) @$(MAKE) -C $(_SOURCE_PATH)/oss -f
OssLint
#定义PCLint所需的变量(这部分设置暂时放在这)
_PCLINT_PATH = E:/PCLink/x/.v8.00N
LINT = $(_PCLINT_PATH)/lint-nt
LINTOPTION = -zero -os($(subst .lob,.txt,$@)) -i$(_PCLINT_PATH)/
LINTOPTIONoss = $(LINTOPTION) $(_OSS_INCLUDE_PATH)
#PCLint的执行入口
OssLint: $(_OSS_LINT_PATH)/Oss_SubSys_
@echo 只完成支撑子系统各模块的Lint。
$(_OSS_LINT_PATH)/Oss_SubSys_:$(_Oss_All_Lint_Objects)
@if not exist $(_OSS_LINT_PATH) $(MKDIR) $(subst /,,$(_OSS_LINT_PATH))
$(LINT) $(LINTOPTION) $(_Oss_All_Lint_Objects) -oo($@)
@echo 输出内容为$@
# PC Lint
_Oss_Common_Lint_Depend += $(addprefix $(_OSS_COMMON_LINT_PATH)/,
$(subst .c,.lob,$(notdir $(_Oss_Common_All_C))))
_Oss_All_Lint_Objects += $(_Oss_Common_Lint_Depend)
$(_OSS_COMMON_LINT_PATH)/%.lob :
$(_OSS_COMMON_SORUCE_PATH)/source/%.c
@echo 开始Lint $<
@if not exist $(_OSS_COMMON_LINT_PATH) $(MKDIR) $(subst
/,,$(_OSS_COMMON_LINT_PATH))
$(ECHO) Linting $@ ......
$(LINT) $(OSS_LINT_CFLAGS) -u +fdi $(LINTOPTIONoss) $< -oo($@)
@echo Done!
#用于PCLint存放lob文件(子系统)
ifeq (TRUE,$(_MAK_EXIST_MULTI_CPU))
_OSS_LINT_PATH = $(_TEMP_PATH)/Oss/Lint/$(_OSS_NET_ELEMENT)/
$(subst _OSS_,,$(_OSS_VERSION_TYPE))/$(_OSS_PHYSICAL_BOARD_NAME)/
$(_OSS_LOGIC_BOARD_NAME)/CPU$(_CURRENT_CPU_INDEX)/
$(_OSS_CPU_NAME)
else
_OSS_LINT_PATH = $(_TEMP_PATH)/Oss/Lint/$(_OSS_NET_ELEMENT)/
$(subst _OSS_,,$(_OSS_VERSION_TYPE))/$(_OSS_PHYSICAL_BOARD_NAME)/
$(_OSS_LOGIC_BOARD_NAME)/$(_OSS_CPU_NAME)
endif
#用于PCLint存放lob文件(子模块)
_OSS_PUB_LINT_PATH = $(_OSS_LINT_PATH)/Pub/OssPub
_OSS_COMMON_LINT_PATH = $(_OSS_LINT_PATH)/Common
_OSS_SCHE_LINT_PATH = $(_OSS_LINT_PATH)/Sche
_OSS_COMM_LINT_PATH = $(_OSS_LINT_PATH)/Comm
_OSS_TIMER_LINT_PATH = $(_OSS_LINT_PATH)/Timer
_OSS_MEM_LINT_PATH = $(_OSS_LINT_PATH)/Mem
_OSS_DEV_LINT_PATH = $(_OSS_LINT_PATH)/Device
_OSS_FILE_LINT_PATH = $(_OSS_LINT_PATH)/File
_OSS_EXCEP_LINT_PATH = $(_OSS_LINT_PATH)/Excep
_OSS_VOS_LINT_PATH = $(_OSS_LINT_PATH)/Vos
_OSS_SDL_LINT_PATH = $(_OSS_LINT_PATH)/Sdl
# 配置DEBUG或者是RELEASE版本的编译标志
ifeq (_OSS_DEBUG,$(_OSS_VERSION_TYPE))
_OSS_VERSION_LINT_FLAG = -D_OSS_DEBUG
else
_OSS_VERSION_LINT_FLAG = -D_OSS_RELEASE
endif
#定义Lint的参数
OSS_LINT_CFLAGS = $(LINT_CFLAGS) $(_OSS_VERSION_LINT_FLAG)
#定义最上层的PCLint参数
TOPLINT_COMMON_CFLAGS = $(COMMON_DEVICE_DEFINE)
-D_REENTRANT -DRW_MULTI_THREAD
$(_MAK_TORNADO_INCLUDE_PATH)
# 为ARM配置编译、链接选项
ifeq ($(_CPU_TYPE),_CPU_ARM)
LINT_CFLAGS = $(TOPLINT_COMMON_CFLAGS)
-DCPU=ARMSA110
endif
#为每种类型的CPU都要设置,这里略过…
经过上面的修改就可以通过makefile调用PC-lint进行代码的检查了(这里只是针对OSS
子系统COMMON模块,其他部分类似)。它将对每一个.C文件做单元检查并生成一个目标
模块.lob,这个过程中产生的所有错误、警告信息都将存入一个同名的.txt文件,并最后把
相应的目标模块连接成子系统的目标模块Oss_SubSys_,这个过程将检查多个模块连
接在一起后是否会存在问题,些处也会把产生的告警存入名为Oss_SubSys_的文件。
需要注意的是PC-lint的所有输出文件(*.lob、*.txt)的名字都是makefile中定义的。
注:lob文件(
Lint Object Module)是一个C或C++模块内的外部信息的总结(二进
制形式) 。PC-lint能使用这些信息来和其它模块比较一致性。
保存检查结果的目录如下所示:
tar
├─temp
├─Oss
├─Lint
├─PCF
├─DEBUG
├─MNIC
├─UPCF
├─ARM
├─Oss_SubSys_
Oss_SubSys_
Common
├─OSS_
OSS_
OSS_Device_
OSS_Device_
Oss_
Oss_
Oss_
Oss_
Oss_
Oss_
4. 平台推广方案(建议)
4.1 推广使用的前提
1、通过对PC-lint在一定范围的试用,形成一个全平台统一的PC-lint检查选项模板,
此模板应按照PC-lint不同的告警级别进行分类,按照从低到高逐渐提高告警级别的方式,
一步一步改善程序质量。
2、根据不同的模板和不同的阶段制定出循序渐进的检查通过标准,如:确定在某一阶
段要求改掉某类告警的百分比。
3、修改版本控制的流程,在模块并入版本前和版本提交测试前,增加通过PC-lint检查
的要求,并按照通过标准进行执行。
4、制定特殊情况的处理流程(在小范围内确定要求关闭某个告警信息)。
注:PC-lint告警级别可以通过-w选项进行设置。
-wLevel:设置错误信息告警级别。
-w0 No messages (except for fatal errors)
-w1 Error messages only -- no Warnings or Informationals.
-w2 Error and Warning messages only
-w3 Error, Warning and Informational messages (this is the default)
-w4 All messages.
4.2 个人的使用方案
如果每一个开发人员都使用PC-lint对各自开发、修改的代码进行检查,可以按照各自
目前使用的开发工具集成PC-lint的做法,这样不仅运行方便,而且对告警的定位和程序修
改都很方便,但是要求必须使用平台统一的PC-lint选项模板,并且只能自行增加变量和头
文件路径信息的定义,不能随意关闭告警信息。
4.3 子系统的检查人的使用方案
采用PC-lint结合makefile的方式,在流程的检查点由指定人员对平台各个子系统的代
码进行检查,并把PC-lint的告警信息发送给相关人员进行处理,通过检查后才能进入下一
个流程。
4.4 特殊情况处理方法
由于各子系统代码风格或个人编程风格的差异,可能存在统一选项文件中的某个选项
在大部分代码中是非常严重,必须排查的,而在某类代码中是轻微的,或者虽然也严重但对
它的修改将会涉及众多文件的修改,这种众多文件的修改在短期内无法完成,对于这类特殊
情况将可以通过在代码中加pclint编译信息,屏蔽相关选项的检查。但对于这类特殊情况,
需要制定流程,要求提交相关的说明。
在代码中加入编译信息屏蔽pclint检查的形式根据代码情况的不同有许多种,但对它的
整理需要多人投入较长时间对pclint进行详细了解才能获得,此处只提供一种最简单的方法,
下面以实例说明:
某段代码中有一句语句
memset((void *)(&(pVar->Data)), 0, sizeof(A_Data));
被pclint检查出warning 545,可疑的&使用,但在程序中经过确认这句话没有问题,而
且由于其他原因的限制不能替换为符合pclint的格式,则在此语句的前后中加入如下pclint
可理解的注释
/*lint –e545 */
memset((void *)(&(pVar->Data)), 0, sizeof(A_Data));
/*lint +545 */
/*lint –e545 */表示在以后的语句中屏蔽e545的检查,/*lint +545 */表示在以后的语句中
回复e545的检查。
通过这种方式即可在局部屏蔽pclint的检查。但程序中可能此语句非常多,对每个语句
加注释会非常麻烦,目前还没有找到此情况下合适的方法。但对于宏,可以在宏定义中添加
注释信息,如对于下面的宏定义
#define DIVZERO(x) ((x) /0)
希望屏蔽e545的检查,则在宏定义中添加注释
#define DIVZERO(x) /*lint -save -e54 */ ((x) /0) /*lint -restore */


发布评论