前言
很多代码问题在编译阶段难以发现,只有在运行时才会暴露。即便是在运行时出现问题了,我们可能仍然需要费一番功夫才能最终找到代码的问题。幸运地是,我们可以利用一个工具在编译之前就可以发现这些问题。有了它,基本可以检查出代码中80%的非逻辑性错误。这就是本文要介绍的主角—PC-lint。
PC-lint简介
PC-Lint 是GIMPEL SOFTWARE公司开发的C/C++软件代码静态分析工具。而所谓静态分析是指在不运行代码的方式下,通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性、可靠性、可维护性等指标—摘自百科。也就是说,利用PC-lint对我们的代码进行扫描分析,在程序运行之前,就可以发现代码中隐藏的问题。PC-lint除了能够发现诸如未初始化变量、数组越界、内存泄漏等问题,还能提出许多对程序运行效率,空间等方面的改进点。下面就简单介绍一下如何使用PC-lint。
如何使用PC-lint
PC-lint能够在Windows、MS-DOS和OS/2平台上使用,Linux平台可使用FlexeLint、Splint等替代工具。本文介绍仅PC-lint的使用。
注:PC-lint为商用软件。
安装方法不在此介绍,和其他普通软件的安装方式一样。安装完成后,在安装目录下会有lint-nt.exe程序。基本使用方法如下:1
lint-nt.exe -u files.lnt #执行之后扫描结果会显示在控制台
其中files.lnt文件中的内容是需要扫描的源代码位置。
例如files.lnt文件内容如下:1
2D:\pclint\lint\test\test.c
D:\pclint\lint\test\main.c
表明将会对main.c和test.c进行静态检查。
如果源文件比较多,那么将源文件添加带files.lnt中是一件很繁琐的事情,我们可以使用命令来得到我们的files.lnt文件:1
dir /S/B *.h *.c > files.lnt
示例程序
我们直接来看一个例子,看看PC-lint到底有哪些能耐。
示例代码
1 | /*main.c*/ |
上面的代码计算数组a的和,并且判断最后和是否等于15。
lnt配置
我们的lnt文件files.lnt配置如下:1
2
3-wlib(0) //对库文件不输出任何错误信息
-iD:\pclint\include //指定头文件路径
D:\pclint\lint\test\main.c //我们的源代码文件
由于我们的代码包含了stdio.h头文件,因此还需要stdio.h头文件,我把它放在了D:\pclint\include,并在lnt文件中指定了头文件的位置。另外,我们只需要扫描我们自己的源代码,因此使用了-wlib(0)来避免对库文件输出告警信息。
扫描代码
执行命令:1
D:\pclint\lint>lint-nt.exe -u .\test\files.lnt>result.txt
这里我们将结果重定向到了result.txt文件中,最后生成的result.txt内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32--- Module: D:\pclint\lint\test\main.c (C)
_
for(loop = 0;loop <= len;loop++)
D:\pclint\lint\test\main.c 10 Warning 574: Signed-unsigned mix with
relational
D:\pclint\lint\test\main.c 10 Info 737: Loss of sign in promotion from int to
unsigned int
_
sum += a[loop];
D:\pclint\lint\test\main.c 12 Warning 530: Symbol 'sum' (line 7) not
initialized
D:\pclint\lint\test\main.c 7 Info 830: Location cited in prior message
_
sum += a[loop];
D:\pclint\lint\test\main.c 12 Warning 661: Possible access of out-of-bounds
pointer (1 beyond end of data) by operator '[' [Reference: file
D:\pclint\lint\test\main.c: lines 8, 10, 12]
D:\pclint\lint\test\main.c 8 Info 831: Reference cited in prior message
D:\pclint\lint\test\main.c 10 Info 831: Reference cited in prior message
D:\pclint\lint\test\main.c 12 Info 831: Reference cited in prior message
_
printf("sum = 15\n");
D:\pclint\lint\test\main.c 16 Warning 534: Ignoring return value of function
'printf(const char *, ...)' (compare with line 271, file
D:\pclint\include\stdio.h)
D:\pclint\include\stdio.h 271 Info 830: Location cited in prior message
_
printf("sum != 15,sum=%d\n",sum);
D:\pclint\lint\test\main.c 21 Warning 534: Ignoring return value of function
'printf(const char *, ...)' (compare with line 271, file
D:\pclint\include\stdio.h)
D:\pclint\include\stdio.h 271 Info 830: Location cited in prior message
问题分析
经过扫描之后,发现了代码中的很多问题。我们一一列举:
- 第10行警告号574,提示有符号数和无符号数混用。我们确实将有符号数loop和无符号数len进行了比较。
- 第12行警告号530,sum未进行初始化。定义sum变量时,并未进行初始化。
- 第12行警告号661,提示可能出现数组越界。我们仔细审查代码就会发现,循环对a进行求值时,其循环条件应该是loop < len而不是loop <= len。
- 第16行,21行提示有返回值没有使用。我们调用printf函数之后,并没有必要使用其返回值,因此我们可以忽略这个警告。
- 第24行提示警告号527,return语句不可到达。由于前面的if-else结构,使得最后的return语句永远无法执行。
问题修改
前面这段代码是可以编译通过,并且运行的,但是经过PC-lint扫描之后却发现如此之多的问题。我们将发现的问题代码进行修改后如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/*main.c*/
/*lint -e{534}*/
int main(void)
{
int a[] = {1,2,3,4,5};
int sum = 0;
unsigned int len = sizeof(a)/sizeof(int);
unsigned int loop;
for(loop = 0;loop < len;loop++)
{
sum += a[loop];
}
if(15 == sum)
{
printf("sum = 15\n");
return 0;
}
else
{
printf("sum != 15,sum=%d\n",sum);
return -1;
}
}
最终PC-lint检查结果如下:1
--- Module: D:\pclint\lint\test\main.c (C)
这里除了修改了我们确定的问题之外,还屏蔽了PC-lint的534号警告,因为我们确认这不会对我们的程序本意造成任何影响,因此使用/*lint -e{534}*/屏蔽了main函数的534号警告。PC-lint屏蔽警告的方法很多,这里不再详述。
总结
通过示例程序可以看出,PC-lint确实能够发现一些隐藏的问题,但实际上它的强大远不止我们前面所看到的那样,利用好PC-lint能够帮助我们在运行程序之前就发现很多难以察觉的问题。本文本意为介绍PC-lint的用途,因此对PC-lint的详细使用并没有做过多介绍,有兴趣的读者可以参考网上的资料进行配置学习,PC-lint所报的警告号都可以通过官方PC-lint错误码查看其含义,帮助修正我们的程序。
问题思考
- 最原始的代码,运行结果是什么?为什么会出现这样的结果?
- 如果将sum定义为全局静态变量,并且将循环条件改为loop < len,还会出现同样的结果吗?为什么?
欢迎在留言区评论留言。