引言

最近在项目中使用了静态程序分析工具PC-Lint,体会到它在项目实施中带给开发人员的方便。PC-Lint是一款针对C/C++语言、windows平台的静态分析工具,FlexeLint是针对其他平台的PC-Lint版本。由于PC-Lint/FlexeLint是商业的程序分析工具,不便于大家对其进行学习和使用,因而下面我将介绍一个针对C语言的开源程序静态分析工具——splint。

静态程序分析

先来说说什么是“静态程序分析(Static program analysis)”,静态程序分析是指使用自动化工具软件对程序源代码进行检查,以分析程序行为的技术,应用于程序的正确性检查、安全缺陷检测、程序优化等。它的特点就是不执行程序,相反,通过在真实或模拟环境中执行程序进行分析的方法称为“动态程序分析(Dynamic program analysis)”。

那在什么情况下需要进行静态程序分析呢?静态程序分析往往作为一个多人参与的项目中代码审查过程的一个阶段,因编写完一部分代码之后就可以进行静态分析,分析过程不需要执行整个程序,这有助于在项目早期发现以下问题:变量声明了但未使用、变量类型不匹配、变量在使用前未定义、不可达代码、死循环、数组越界、内存泄漏等。下图说明了静态程序分析在进行项目编码过程中所处的位置:

从上图可以知道,静态分析工具在代码通过编译之后再对代码进行分析。我们会问:静态分析工具与编译器相比,所做的工作有什么不同?静态分析工具相比编译器,对代码进行了更加严格的检查,像数组越界访问、内存泄漏、使用不当的类型转换等问题,都可以通过静态分析工具检查出来,我们甚至可以在分析工具的分析标准里定义代码的编写规范,在检测到不符合编写规范的代码时抛出告警,这些功能都是编译器没有的。

既然静态分析工具发挥了不小的作用,何不在编译器里兼备静态分析的功能?对于这个问题,S. C. Johnson(他是最古老的静态分析工具Lint的作者)在其1978年发表的论文《Lint, a C Program Checker》中给出了他的答案:“Lint与C编译器在功能上的分离既有历史原因,也有现实的意义。编译器负责把C源程序快速、高效地转变为可执行文件,不对代码做类型检查(特别是对分别编译的程序),有益于做到快速与高效。而Lint没有“高效”的要求,可以花更多时间对代码进行更深入、仔细的检查。”

针对空指针提取、未定义变量使用、类型转换、内存管理、函数接口定义等,我们可以在静态分析工具里制定不同的检测标准,以下曲线图说明了在使用splint进行分析时,检测标准与splint运行的开销所对应的关系,从另一个角度看,也说明了静态分析工具与编译器的关系:

splint

掌握了“静态分析”等概念之后,我们再来看splint。

在Linux命令行下,splint的使用很简单,检测文件*.c,只要这样使用就可以了:

splint *.c

1.splint消息

我们通过以下例子来认识典型的splint告警信息:

 1 //splint_msg.c
 2 int func_splint_msg1(void)
 3 {
 4 int a;
 5 return0;
 6 }
 7 int func_splint_msg2(void)
 8 {
 9 int* a = (int*)malloc(sizeof(int));
10     a = NULL;
11 return0;
12 }

运行splint splint_msg.c之后,我们来看输出的告警信息:

splint_msg.c: (in function func_splint_msg1)
splint_msg.c:4:6: Variable a declared but not used
  A variable is declared but never used. Use /*@unused@*/ in front of
  declaration to suppress message. (Use -varuse to inhibit warning)splint_msg.c: (in function func_splint_msg2)
splint_msg.c:10:2: Fresh storage a (type int *) not released before assignment:
                      a = NULLA memory leak has been detected. Storage allocated locally is not releasedbefore the last reference to it is lost. (Use -mustfreefresh to inhibitwarning)splint_msg.c:9:37: Fresh storage a createdFinished checking --- 2 code warnings

蓝色字体部分:给出告警所在函数名,在函数的第一个警告消息报告前打印;

红色字体部分:消息的正文,文件名、行号、列号显示在的警告的正文前;

黑色字体部分:是有关该可疑错误的详细信息,包含一些怎样去掉这个消息的信息;

绿色字体部分:给出格外的位置信息,这里消息给出了是在哪里申请了这个可能泄露的内存。

2.检查控制

splint提供了三种方式可进行检查的控制,分别是.splintrc配置文件、flags标志和格式化注释。

flags:splint支持几百个标志用来控制检查和消息报告,使用时标志前加’+‘或’-’,'+'标志开启这个标志,'-'表示关闭此标志,下面例子展示了flags标志的用法:

splint -showcol a.c   //在检测a.c时,告警消息中列数不被打印
splint -varuse  a.c   //在检测a.c时,告警消息中未使用变量告警不被打印

.splintrc配置文件:在使用源码安装splint之后,.splintrc文件将被安装在主目录下,.splintrc文件中对一些标志作了默认的设定,命令行中指定的flags标志会覆盖.splintrc文件中的标志。

格式化注释:格式化注释提供一个类型、变量或函数的格外的信息,可以控制标志设置,增加检查效果,所有格式化注释都以/*@开始,@*/结束,比如在函数参数前加/*@null@*/,表示该参数可能是NULL,做检测时,splint会加强对该参数的值的检测。

3.检测分析内容

1.解引用空指针(Null Dereferences)

在Unix操作系统中,解引用空指针将导致我们在程序运行时产生段错误(Segmentation fault),一个简单的解引用空指针例子如下:

1 //null_dereferences.c
2 int func_null_dereferences(void)
3 {
4 int* a = NULL;
5 return*a;
6 }

执行splint null_dereference.c命令,将产生以下告警消息:

null_dereference.c: (in function func_null_dereferences)
null_dereference.c:5:10: Dereference of null pointer a: *aA possibly null pointer is dereferenced.  Value is either the result of afunction which may return null (in which case, code should check it is notnull), or a global, parameter or structure field declared with the nullqualifier. (Use -nullderef to inhibit warning)null_dereference.c:4:11: Storage a becomes nullFinished checking --- 1 code warnin

2.类型(Types)

我们在编程中经常用到强制类型转换,将有符号值转换为无符号值、大范围类型值赋值给小范围类型,程序运行的结果会出无我们的预料。

1 //types.c
2 void splint_types(void)
3 {
4 short a =0;
5 long b =32768;
6     a = b;
7 return;
8 }

执行splint types.c命令,将产生以下告警消息:

types.c: (in function splint_types)
types.c:6:2: Assignment of longint to shortint: a = bTo ignore type qualifiers in type comparisons use +ignorequals.Finished checking ---1 code warning

3.内存管理(Memory Management)

C语言程序中,将近半数的bug归功于内存管理问题,关乎内存的bug难以发现并且会给程序带来致命的破坏。由内存释放所产生的问题,我们可以将其分为两种:

  • 当尚有其他指针引用的时候,释放一块空间
1 //memory_management1.c
2 void memory_management1(void)
3 {
4 int* a = (int*)malloc(sizeof(int));
5 int* b = a;
6     free(a);
7 *b =0;
8 return;
9 }

在上面这个例子中,指针a与b指向同一块内存,但在内存释放之后仍对b指向的内容进行赋值操作,我们来看splint 
memory_management1.c的结果:

memory_management1.c: (in function memory_management1)
memory_management1.c:7:3: Variable b used after being releasedMemory is used after it has been released (either by passing as an only paramor assigning to an only global). (Use -usereleased to inhibit warning)memory_management1.c:6:7: Storage b released
memory_management1.c:7:3: Dereference of possibly null pointer b: *bA possibly null pointer is dereferenced.  Value is either the result of afunction which may returnnull (in which case, code should check it is notnull), or a global, parameter or structure field declared with the nullqualifier. (Use -nullderef to inhibit warning)memory_management1.c:5:11: Storage b may become nullFinished checking ---2 code warnings

检查结果中包含了两个告警,第一个指出我们使用了b指针,而它所指向的内存已被释放;第二个是对解引用空指针的告警。

  • 当最后一个指针引用丢失的时候,其指向的空间尚未释放
1 //memory_management2.c
2 void memory_management2(void)
3 {
4 int* a = (int*)malloc(sizeof(int));
5      a = NULL;
6 return;
7 }

这个例子中内存尚未释放,就将指向它的唯一指针赋值为NULL,我们来看splint memory_management2.c的检测结果:

memory_management2.c: (in function memory_management2)
memory_management2.c:5:2: Fresh storage a (type int*) not released before assignment:a = NULLA memory leak has been detected. Storage allocated locally is not releasedbefore the last reference to it is lost. (Use -mustfreefresh to inhibitwarning)memory_management2.c:4:37: Fresh storage a createdFinished checking ---1 code warning

splint抛出一个告警:类型为int*的a在进行a = NULL赋值前没有释放新分配的空间。

4.缓存边界(Buffer Sizes)

splint会对数组边界、字符串边界作检测,使用时需要加上+bounds的标志,我们来看下面的例子:

1 //bounds1.c
2 void bounds1(void)
3 {
4 int a[10];
5     a[10] =0;
6 return;
7 }

使用splint +bounds bounds1.c命令对其进行检测,结果如下:

bounds1.c: (in function bounds1)
bounds1.c:5:2: Likely out-of-bounds store: a[10]Unable to resolve constraint:requires 9>=10needed to satisfy precondition:requires maxSet(a @ bounds1.c:5:2) >=10A memory write may write to an address beyond the allocated buffer. (Use-likelyboundswrite to inhibit warning)Finished checking ---1 code warning

告警消息提示数组越界,访问超出我们申请的buffer大小范围。再看一个例子:

 1 //bounds2.c
 2 void bounds2(char* str)
 3 {
 4 char* tmp = getenv("HOME");
 5 if(tmp != NULL)
 6     {
 7         strcpy(str, tmp);
 8     }
 9 return;
10 }

不对这个例子进行详细检查,可能我们不能发现其中隐含的问题,执行splint +bounds bounds2.c之后,会抛出如下告警:

bounds2.c: (in function bounds2)
bounds2.c:7:3: Possible out-of-bounds store: strcpy(str, tmp)Unable to resolve constraint:
    requires maxSet(str @ bounds2.c:7:10) >= maxRead(getenv("HOME") @bounds2.c:4:14)needed to satisfy precondition:requires maxSet(str @ bounds2.c:7:10) >= maxRead(tmp @ bounds2.c:7:15)derived from strcpy precondition: requires maxSet(<parameter 1>) >=maxRead(<parameter 2>)A memory write may write to an address beyond the allocated buffer. (Use-boundswrite to inhibit warning)Finished checking ---1 code warning

告警消息提示我们:在使用strcpy(str, tmp)进行字符串复制时,可能出现越界错误,因为str的大小可能不足以容纳环境变量“HOME”对应的字符串。绿色字体的内容指示了如何消除告警消息。

 

小结

这里仅给出了splint检查的4种检测:解引用空指针、类型、内存管理、缓存边界,除此之外,splint还对宏(Macros)、函数接口(Function Interfaces)、控制流(Control Flow)等内容作检测,很多检测标志和格式化注释都未在本文中提到,更详细的内容请查看splint使用手册。

splint的学习与使用相关推荐

  1. 代码静态分析工具-splint的学习与使用[转]

    代码静态分析工具--splint的学习与使用[转] 引言 最近在项目中使用了静态程序分析工具PC-Lint,体会到它在项目实施中带给开发人员的方便.PC-Lint是一款针对C/C++语言.window ...

  2. 代码静态分析工具——splint的学习与使用

    引言 最近在项目中使用了静态程序分析工具PC-Lint,体会到它在项目实施中带给开发人员的方便.PC-Lint是一款针对C/C++语言.windows平台的静态分析工具,FlexeLint是针对其他平 ...

  3. 软件测试代码静态分析(splint)

    转载 http://www.cnblogs.com/bangerlee/archive/2011/09/07/2166593.html 代码静态分析工具--splint的学习与使用 引言 最近在项目中 ...

  4. linux 提高代码质量的工具

    很多IT公司对于软件开发都有严格的分工,这包括设计.测试.服务支持等等.但是,我一直都认为只有开发者才是真正对软件质量负责的人.没有好的软件设计,软件质量基本上是无从谈起.当然,要做到这一点是需要额外 ...

  5. linux vim配置c,Linux入门学习教程:GNU C及将Vim打造成C/C++的半自动化IDE

    C语言在Linux系统中的重要性自然是无与伦比.不可替代,所以我写Linux江湖系列不可能不提C语言.C语言是我的启蒙语言,感谢C语言带领我进入了程序世界.虽然现在不靠它吃饭,但是仍免不了经常和它打交 ...

  6. Docker学习二:Docker镜像与容器

    前言 本次学习来自于datawhale组队学习: 教程地址为: https://github.com/datawhalechina/team-learning-program/tree/master/ ...

  7. 零基础学习C++系列课程(一) 持续更新中

    目录 第 1 节:课程目标 项目 1:C++基础编程-黑客攻击系统(含 6 个子项目) 项目 2:C++基础编程-人工智能之地形导航系统 项目 3:C++基础编程-人工智能之双色球预测系统 项目 4: ...

  8. 学习游戏服务器开发必看,C++游戏服务器开发常用工具介绍

    C++游戏服务器开发常用工具介绍 在软件开发过程中需要使用的工具类型实属众多,从需求建模到软件测试,从代码编译到工程管理,这些工具都对项目有着不可替代的作用.庄子有云,"吾生也有涯,而知也无 ...

  9. 漫谈C语言及如何学习C语言(转)

    云风最近写了一篇博客<C语言的前世今生>.作为长期使用C语言开发网络游戏服务器的程序员,云风是有理由写这样一篇文字,不过还是感觉谈的不够深入,C语言在业界使用的现状没有怎么描写,有些意犹未 ...

最新文章

  1. PHP 截取字符串专题
  2. Hive - HWI 简单使用
  3. python3.9性能_Python 3.9 性能优化:更快的 list()、dict() 和 range() 等内置类型
  4. python中字符串函数的用法_python中字符串内置函数的用法介绍(代码)
  5. 使用程序简单查询IP地址
  6. GO语言学习之路13
  7. Linux下MySQL 5.5的修改字符集编码为UTF8(彻底解决中文乱码问题)
  8. 计算机网络——网络安全基础笔记
  9. CSC公派|小红本及小黄本的办理
  10. springboot项目java生成kml文件
  11. 2022国二计算机office 考试考试秘籍总结大全
  12. 学校计算机房网络的拓扑结构一般采用,XX学校机房建设规划方案
  13. 共享文件夹加密专家_文件加密_公司如何防止员工内部泄密?
  14. WINCE 矩阵键盘 介绍
  15. JDBC读取Oracle的US7ASCII编码中文乱码及不同编码下汉字占用字节的问题
  16. Mysql关键特性-插入缓冲 (Insert Buffer)
  17. 前字节跳动程序员 28 岁提前退休引热议,网友:我也想
  18. Ubuntu 16.04 + cuda-8.0 + cudnn-6.0 + Tensorflow1.4和Caffe(极其简单)
  19. 基于低加密指数广播攻击(Hastad攻击)的更深一步学习
  20. NLP-基础任务-中文分词算法(2)-基于词典:基于N-gram语言模型的分词算法【基于词典的分词方法】【利用维特比算法求解最优路径】【比机械分词精度高】【OOV:基于现有词典,不能进行新词发现处理】

热门文章

  1. (10):始终要覆盖toString
  2. 修正牛顿法matlab,牛顿算法及其改进【阻尼牛顿法、修正牛顿法】
  3. A*算法 puzzle8数码
  4. 第三章:zigbee学习笔记之物理层和mac层帧格式分析
  5. 射洪县住房城乡建设局积极推进智慧城市建设
  6. 浏览器导航页被篡改解决办法
  7. php 生成图片并压缩保存到本地或者输出到网页imagejpeg方法
  8. DeepMind 研发的围棋 AI AlphaGo 是如何下棋的?
  9. 使用JS+socket.io+WebRTC+nodejs+express搭建一个简易版远程视频聊天
  10. This dream is just right