[BuildRelease]C++代码静态分析工具splint

2023-11-12

转自: http://www.cnblogs.com/bangerlee/archive/2011/09/07/2166593.html

引言

最近在项目中使用了静态程序分析工具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 effort-benefit curve

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 return 0;
6 }
7 int func_splint_msg2(void)
8 {
9 int* a = (int*)malloc(sizeof(int));
10 a = NULL;
11 return 0;
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 = NULL
A memory leak has been detected. Storage allocated locally is not released
before the last reference to it is lost. (Use -mustfreefresh to inhibit
warning)
splint_msg.c:9:37: Fresh storage a created

Finished 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: *a
A possibly null pointer is dereferenced. Value is either the result of a
function which may return null (in which case, code should check it is not
null), or a global, parameter or structure field declared with the null
qualifier. (Use -nullderef to inhibit warning)
null_dereference.c:4:11: Storage a becomes null

Finished 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 long int to short int: a = b
To 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 released
Memory
is used after it has been released (either by passing as an only param
or 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: *b
A possibly
null pointer is dereferenced. Value is either the result of a
function which may
return null (in which case, code should check it is not
null), or a global, parameter or structure field declared with the null
qualifier. (Use
-nullderef to inhibit warning)
memory_management1.c:5:11: Storage b may become null

Finished 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
= NULL
A memory leak has been detected. Storage allocated locally
is not released
before the last reference to it
is lost. (Use -mustfreefresh to inhibit
warning)
memory_management2.c:4:37: Fresh storage a created

Finished 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 >= 10
needed to satisfy precondition:
requires maxSet(a @ bounds1.c:
5:2) >= 10
A 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使用手册

不管pc-lint、splint等静态程序分析工具的功能多么强大,它们对程序的检查也有疏漏的地方,工具的使用并不能提高我们的编程能力,我们更应该通过它们学习各种编码错误和代码隐患,凭积累的编码知识把程序隐患扼杀在摇篮里。

你在项目实施中是否遇到过隐藏的bug,导致返工呢?在你的项目中是否使用了静态程序分析工具,它起到多大的作用?说出来与大家一块分享吧~

Reference:

《Splint Manual》

《Lint, a C Program Checker》

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[BuildRelease]C++代码静态分析工具splint 的相关文章

随机推荐

  • 强化学习 最前沿之graph policy gradients

    强化学习 Zee最前沿系列 深度强化学习作为当前发展最快的方向 可以说是百家争鸣的时代 针对特定问题 针对特定环境的文章也层出不穷 对于这么多的文章和方向 如果能撇一隅 往往也能够带来较多的启发 本系列文章 主要是针对当前较新的深度强化学习
  • 使用SQL语句操作数据表和管理表中的数据

    一 使用SQL语句操作数据表 表名是可以在数据库中唯一确定的一张表 1 创建表 语法 create table 表的名字 列名1 数据类型 列名2 数据类型 列名3 数据类型 注意 创建表之后括号后应该用分号结束 并且在列名和数据类型的最后
  • Windows平台下安装与配置MySQL

    免费下载网址 https dev mysql com downloads windows installer 8 0 html 版本选择 社区版8 0 20 双击安装包 选择Developer Default 下一步 点Execute执行
  • C语言 - static inline

    2019 07 16 今天在看DPDK负载均衡的实例代码中 通过函数跳转 看到官方API后 发现了static inline这个关键字 这个我只是在很早之前知道inline是内联的 可以不进行压栈 但是static毕竟是限制函数的作用域的啊
  • Sentinel实现熔断与限流

    文章目录 一 Sentinel是什么 1 简介 2 对比 3 Linux安装 二 初始化演示工程 1 新建module cloudalibaba sentinel service8401 2 pom文件 3 application yml
  • 个人总结:京东技术体系员工级别划分及薪资区间

    管理层级 序列层级 职衔 对应T序 薪资区间 技术 M5 CXO M5 VP M4 3 高级总监 M4 2 总监 T5 40 50k M4 1 副总监 T5 35 45k M3 高级经理 T4 2 30 40k M2 2 经理 T4 1 2
  • 【ReID】【代码注释】采样器 deep-person-reid/samplers.py

    源码URL https github com michuanhaohao deep person reid blob master samplers py 采样器读源码注释如下 from future import absolute imp
  • Java三大器之拦截器(Interceptor)的实现原理及代码示例

    前言 前面2篇博客 我们分析了Java中过滤器和监听器的实现原理 今天我们来看看拦截器 1 拦截器的概念 java里的拦截器是动态拦截Action调用的对象 它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码 也可以在一个
  • ImportError: cannot import name ‘mean_absolute_percentage_error‘ from ‘sklearn.metrics‘

    在使用mean absolute percentage error时 导入模块报错 from sklearn metrics import mean absolute percentage error 报错信息 ImportError ca
  • python将字符串转为字典(将str类型还原为dict字典类型)

    有三种方法 eval 字符串 yaml load 字符串 Loader yaml FullLoader ast literal eval 字符串 但是要注意 转换之前 原始的字典中key与value必须是python原生支持的类型 不能是d
  • 代码实例讲解:卷积神经网络程序细节(附完整代码)

    1 导入数据集和tensorflow包 from tensorflow examples tutorials mnist import input data import tensorflow as tf 2 初步探索mnist数据集的内容
  • 高通平台中用devicetree注册设备及驱动匹配

    1 在设备树文件 dts 中的相应节点下添加要注册设备的节点 soc plf char dev mytest plf char dev compatible mytest plf char dev 定义好的设备树源文件 dts文
  • Python数据可视化(三)绘制统计图形大全

    3 1 柱状图 以 Python 代码的形式讲解柱状图的绘制原理 这里重点讲解 bar 函数的使用方法 代码 import matplotlib as mpl import matplotlib pyplot as plt mpl rcPa
  • 网络编程——TFTP协议(基于UDP)

    目录 1 tftp协议概述 2 tftp下载模型 3 tftp协议分析 代码 1 tftp协议概述 简单文件传输协议 适用于在网络上进行文件传输的一套标准协议 使用UDP传输 特点 是应用层协议 基于UDP协议实现 数据传输模式 octet
  • 小例子:实现互斥锁,自旋锁

    小例子实现互斥锁 自旋锁 在多线程并发访问临界区时 使用上面三种操作 可以实现并发访问 首先创建是个线程 for i 0 i
  • 开源主流分布式文件系统简单介绍

    文章目录 一 分布式文件系统简介 1 特点 2 主要指标及分类对比 3 AFS与NFS 二 开源分布式文件系统 1 GFS 1 GFS与NFS AFS的区别 2 BigTable 3 Chubby 4 特点1 2 HDFS 1 HDFS与C
  • 多线程与多进程(5)

    概念上 一 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动 是系统进行资源分配和调度的一个独立单位 二 线程是进程的一个实体 是CPU调度和分派的基本单位 它是比进程更小的能独立运行的基本单位 线程自己基本上不拥有系统资源 只
  • AlexNet网络详解及各层作用

    AlexNet 图中所给关于卷积核的尺寸来自于Alex在2012年发表的经典文章 Alex在2012年提出的alexnet网络结构模型引爆了神经网络的应用热潮 并赢得了2012届图像识别大赛的冠军 使得CNN成为在图像分类上的核心算法模型
  • CSDN----Markdown:---文字颜色、大小和字体设置

    Markdown是一种轻量级标记语言 创始人为约翰 格鲁伯 英语 John Gruber 它允许人们使用易读易写的纯文本格式编写文档 然后转换成有效的XHTML 或者HTML 文档 这种语言吸收了很多在电子邮件中已有的纯文本标记的特性 一
  • [BuildRelease]C++代码静态分析工具splint

    转自 http www cnblogs com bangerlee archive 2011 09 07 2166593 html引言 最近在项目中使用了静态程序分析工具PC Lint 体会到它在项目实施中带给开发人员的方便 PC Lint