查看原文
其他

代码中%80的非逻辑性代码都可以被它发现

守望先生 编程珠玑 2019-04-02




前言

很多代码问题在编译阶段难以发现,只有在运行时才会暴露。即便是在运行时出现问题了,我们可能仍然需要费一番功夫才能最终找到代码的问题。幸运地是,我们可以利用一个工具在编译之前就可以发现这些问题。有了它,基本可以检查出代码中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程序。基本使用方法如下:

1lint-nt.exe -u files.lnt #执行之后扫描结果会显示在控制台

其中files.lnt文件中的内容是需要扫描的源代码位置。
例如files.lnt文件内容如下:

1D:\pclint\lint\test\test.c
2D:\pclint\lint\test\main.c

表明将会对main.c和test.c进行静态检查。
如果源文件比较多,那么将源文件添加带files.lnt中是一件很繁琐的事情,我们可以使用命令来得到我们的files.lnt文件:

1dir /S/B *.h *.c > files.lnt 

示例程序

我们直接来看一个例子,看看PC-lint到底有哪些能耐。

示例代码

1/*main.c*/
2#include <stdio.h>
3int main(void)
4
{
5
6    int a[] = {1,2,3,4,5};
7    int sum;
8    unsigned int len = sizeof(a)/sizeof(int); 
9    int loop;
10    for(loop = 0;loop <= len;loop++)
11    {
12        sum += a[loop];
13    }
14    if(15 == sum)
15    {
16        printf("sum = 15\n");
17        return 0;
18    }   
19    else
20    {
21        printf("sum != 15,sum=%d\n",sum);
22        return -1;
23    }
24}

上面的代码计算数组a的和,并且判断最后和是否等于15。

lnt配置

我们的lnt文件files.lnt配置如下:

1-wlib(0)     //对库文件不输出任何错误信息
2-iD:\pclint\include  //指定头文件路径
3D:\pclint\lint\test\main.c //我们的源代码文件

由于我们的代码包含了stdio.h头文件,因此还需要stdio.h头文件,我把它放在了D:\pclint\include,并在lnt文件中指定了头文件的位置。另外,我们只需要扫描我们自己的源代码,因此使用了-wlib(0)来避免对库文件输出告警信息。

扫描代码

执行命令:

1D:\pclint\lint>lint-nt.exe -u .\test\files.lnt>result.txt

这里我们将结果重定向到了result.txt文件中,最后生成的result.txt内容如下:

1--- Module:   D:\pclint\lint\test\main.c (C)
2                            _
3    for(loop = 0;loop <= len;loop++)
4D:\pclint\lint\test\main.c  10  Warning 574: Signed-unsigned mix with
5    relational
6D:\pclint\lint\test\main.c  10  Info 737: Loss of sign in promotion from int to
7    unsigned int
8                      _
9        sum += a[loop];
10D:\pclint\lint\test\main.c  12  Warning 530: Symbol 'sum' (line 7) not
11    initialized
12D:\pclint\lint\test\main.c  7  Info 830: Location cited in prior message
13                      _
14        sum += a[loop];
15D:\pclint\lint\test\main.c  12  Warning 661: Possible access of out-of-bounds
16    pointer (1 beyond end of data) by operator '[' [Reference: file
17    D:\pclint\lint\test\main.c: lines 81012]
18D:\pclint\lint\test\main.c  8  Info 831: Reference cited in prior message
19D:\pclint\lint\test\main.c  10  Info 831: Reference cited in prior message
20D:\pclint\lint\test\main.c  12  Info 831: Reference cited in prior message
21                            _
22        printf("sum = 15\n");
23D:\pclint\lint\test\main.c  16  Warning 534: Ignoring return value of function
24    'printf(const char *, ...)' (compare with line 271, file
25    D:\pclint\include\stdio.h)
26D:\pclint\include\stdio.h  271  Info 830: Location cited in prior message
27                                        _
28        printf("sum != 15,sum=%d\n",sum);
29D:\pclint\lint\test\main.c  21  Warning 534: Ignoring return value of function
30    'printf(const char *, ...)' (compare with line 271, file
31    D:\pclint\include\stdio.h)
32D:\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/*main.c*/
2#include <stdio.h>
3/*lint -e{534}*/
4int main(void)
5
{
6
7    int a[] = {1,2,3,4,5};
8    int sum = 0;
9    unsigned int len = sizeof(a)/sizeof(int); 
10    unsigned int loop;
11    for(loop = 0;loop < len;loop++)
12    {
13        sum += a[loop];
14    }
15    if(15 == sum)
16    {
17        printf("sum = 15\n");
18        return 0;
19    }   
20    else
21    {
22        printf("sum != 15,sum=%d\n",sum);
23        return -1;
24    }
25}

最终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,还会出现同样的结果吗?为什么?

欢迎在留言区评论留言。


推荐阅读:

Linux常用命令-解压缩篇

C语言入坑指南-“悬挂”else

值得开发者收藏关注的网站

变长参数探究





    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存