如何在编码阶段减少代码中的bug?
前言
作为一名合格的程序员,不写bug是不可能的。如何花费最少的时间来修复bug呢?
在编码阶段借助一些静态分析工具往往可以事半功倍,减少代码中的bug。
静态分析工具能够在代码未运行的情况下分析源代码,发现代码中的bug。在C/C++程序中,静态分析工具可以发现程序错误,如空指针取消引用、内存泄漏、被零除、整数溢出、越界访问、初始化前使用等。
编译器中的静态分析
编译器的目标是生成可执行文件,所以,他们并不关注静态代码分析。
但是,随着编译器的慢慢完善,在静态分析方面也做得越来越好。
比如,当我们编译代码时,有时候编译器会产生很多烦人的警告。大多数时候,这些警告并不会给程序造成影响。因此,很多人并不会关注这些警告。
不过,我们应该充分信任编译器。毕竟,没有人比编译器更了解这门语言。
因此,我们必须花一些时间来认真检查编译器产生的警告。这比起花费几个小时甚至几天去解一个bug代价要小的多。
例如,看下下面的代码,你觉得他会打印“ON” 还是 “OFF”呢?
#include <stdio.h>
#define ON 0xFF
#define OFF 0x00
void print_message(char status)
{
if (status == ON)
printf("ON\n");
else
printf("OFF\n");
}
int main(int argc, const char *argv[])
{
print_message(ON);
return 0;
}
$ gcc main.c -o main
$ ./main
OFF!
结果出人意料!我第一次也错误的认为这段代码会打印“ON”。
如果我们用Clang编译,又有什么结果呢?
Clang main.c -o main
main.c:8:16: warning: comparison of constant 255 with expression of type 'char' is always
false [-Wtautological-constant-out-of-range-compare]
if (status == ON)
~~~~~~ ^ ~~
1 warning generated.
Clang是一个优秀的静态分析器,能够分析代码中潜在的问题。对于上面的问题,GCC 在编译时加上-Wall 和-Wpedantic编译选项也可以分析出bug。
$ gcc main.c -o main -Wall -Wpedantic
main.c: In function ‘main’:
main.c:3:13: warning: overflow in implicit constant conversion [-Woverflow]
#define ON 0xFF
^
main.c:16:19: note: in expansion of macro ‘ON’
print_message(ON);
^
不过,Clang和GCC的主要任务是编译代码,静态分析也并不是在每次编译时都需要,而且编译器在做静态分析时需要花费大量的时间。这就是为什么我们需要一个专门的静态代码分析工具。
cppcheck简介
Cppcheck是一个针对C/C++代码的静态分析工具,专注于检测未定义的行为和危险的编码行为。比如空指针,除零,整数溢出,无效的移位操作,无效的转换,STL的无效用法,内存管理,空指针引用,越界检查,未初始化的变量,未使用或者重复的代码等。
Cppcheck是一个开源项目,托管在Sourceforge和GitHub上,支持GNU/Linux、Windows和Mac OS操作系统。
安装Cppcheck
可以通过以下命令,在线安装Cppcheck。
sudo apt install cppcheck
也可以直接下载源代码,手动编译安装:
wget https://github.com/danmar/cppcheck/archive/1.90.tar.gz
$ tar xfv 1.90.tar.gz
$ cd cppcheck-1.90/
$ make MATCHCOMPILER=yes FILESDIR=/usr/share/cppcheck HAVE_RULES=yes -j4
$ sudo make MATCHCOMPILER=yes FILESDIR=/usr/share/cppcheck HAVE_RULES=yes install
$ cppcheck --version
Cppcheck 1.90
使用Cppcheck分析代码
例子1
下面,我们通过一个例子来介绍Cppcheck的使用方法。你能找出以下代码中的两个bug吗?
void calc(void)
{
int buf[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int result;
int i;
for (i = 0; i <= 10; i++) {
result += buf[i];
}
}
int main(void)
{
calc();
return 0;
}
使用GCC编译代码,并没有报出任何警告和错误。
$ gcc -Wall -Wextra -Werror -Wpedantic main.c -o main
$ ls main
main
Clang分析出了其中的一个bug。
$ clang -Wall -Wextra -Werror -Wpedantic -Weverything main.c -o main
main.c:8:9: error: variable 'result' is uninitialized when used here [-Werror,-Wuninitialized]
result += buf[i];
^~~~~~
main.c:4:15: note: initialize the variable 'result' to silence this warning
int result;
^
= 0
1 error generated.
而cppcheck找出了全部的bug。
$ cppcheck main.c
Checking main.c ...
main.c:8:22: error: Array 'buf[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]
result += buf[i];
^
main.c:8:9: error: Uninitialized variable: result [uninitvar]
result += buf[i];
^
例子2
我们用cppcheck 分析下Busybox看看有什么样的结果。
$ wget https://busybox.net/downloads/busybox-1.31.1.tar.bz2
$ tar xfv busybox-1.31.1.tar.bz2
$ cd busybox-1.31.1/
$ cppcheck . 2>&1 | tee cppcheck.log
...
$ cat cppcheck.log | grep error | wc -l
146
Busybox的最新版本中有超过140个可能的bug(在我写这篇文章的时候)。有些错误可能是误报,不过有几个是可以分析下的。
$ cat cppcheck.log | grep "Uninitialized variable"
archival/libarchive/bz/blocksort.c:1034:20: error: Uninitialized variable: origPtr [uninitvar]
archival/libarchive/bz/compress.c:235:18: error: Uninitialized variable: ll_i [uninitvar]
archival/libarchive/bz/compress.c:679:20: error: Uninitialized variable: origPtr [uninitvar]
archival/libarchive/decompress_bunzip2.c:165:20: error: Uninitialized variable: runCnt [uninitvar]
console-tools/loadfont.c:146:6: error: Uninitialized variable: height [uninitvar]
...
$ cat cppcheck.log | grep "out of bounds"
util-linux/fdisk_sgi.c:138:10: error: Array 'freelist[17]' accessed at index 17, which is out of bounds. [arrayIndexOutOfBounds]
util-linux/fdisk_sgi.c:138:10: note: Array index out of bounds
util-linux/fdisk_sgi.c:139:10: error: Array 'freelist[17]' accessed at index 17, which is out of bounds. [arrayIndexOutOfBounds]
util-linux/fdisk_sgi.c:139:10: note: Array index out of bounds
util-linux/volume_id/iso9660.c:114:15: error: Buffer is accessed out of bounds: hs->id [bufferAccessOutOfBounds]
...
$ cat cppcheck.log | grep "Resource leak"
scripts/kconfig/confdata.c:376:4: error: Resource leak: out [resourceLeak]
cppcheck扩展插件
Cppcheck还可以通过使用正则表达式创建新的检查规则来扩展,甚至可以通过用Python编写的模块来扩展。
此外,还有针对Eclipse、Visual Studio、Code::Blocks、Sublime Text和QtCreator等几种流行开发工具的cppcheck插件。

vim编辑器 插件链接:https://github.com/vim-syntastic/syntastic

总结
静态代码分析有时候可能会产生误报,但是cppcheck很好的平衡了真实bug和误报的数量。因此,建议大家可以在个人的开发工具中集成cppcheck静态分析工具。虽然它并不会解决你所有的问题,但是,它肯定有助于提高你代码的质量,并且减少你花在修正bug上的时间。
如何在编码阶段减少代码中的bug?的更多相关文章
- FindBugs 入门——帮你减少代码中的bug数
FindBugs 入门 FindBugs 作用 开发人员在开发了一部分代码后,可以使用FindBugs进行代码缺陷的检查.提高代码的质量,同时也可以减少测试人员给你报的bug数. 代码缺陷分类 根据缺 ...
- 写代码的心得,怎么减少编程中的 bug?
遭遇 bug 的时候,理性的程序员会说:这个 bug 能复现吗? 自负型:这不可能,在我这是好好的. 经验型:不应该,以前怎么没问题? 幻想型:可能是数据有问题. 无辜型:我好几个星期都没碰这块代码了 ...
- [转] 怎么减少编程中的 bug?
[转]http://macshuo.com/?p=1361 怎么减少编程中的 bug? Posted on 2016 年 2 月 17 日 为什么要编程?因为代码没在那里.创造一个世界是如此让人着迷, ...
- 如何减少代码中的if-else嵌套
实际项目中,往往有大量的if-else语句进行各种逻辑校验,参数校验等等,大量的if-else,语句使代码变得臃肿且不好维护,本篇文章结合我自己的经验,就减少if-else语句给出以下几种方案,分别适 ...
- 论减少代码中return语句的骚操作
一.写作背景 最近组内在推行checkstyle代码规范的检测,关于checkstyle的介绍可以参考:https://checkstyle.sourceforge.io, 在按照checkstyle ...
- 有效的减少代码中太多的if、else?-策略模式
写这篇文章的目的和上一篇单例模式一样,策略模式也是一种常用的设计模式,太多的if-else不仅看着不太美观而且不好维护,对于自己来说也等于复习了一遍策略模式.先说一下策略 模式的定义: 策略模式封装了 ...
- 程序员怎样在复杂代码中找 bug?(简单)
分享下我的debug的经验 1. 优先解决那些可重现的,可重现的bug特别好找,反复调试测试就好了,先把好解决的干掉,这样最节约时间. 2. 对于某些bug没有头绪或者现象古怪不知道从哪里下手,找有经 ...
- 程序员新人怎样在复杂代码中找 bug?
分享下我的debug的经验 1. 优先解决那些可重现的,可重现的bug特别好找,反复调试测试就好了,先把好解决的干掉,这样最节约时间. 2. 对于某些bug没有头绪或者现象古怪不知道从哪里下手,找有经 ...
- (转)程序员新人怎样在复杂代码中找 bug?
我曾经做了两年大型软件的维护工作,那个项目有10多年了,大约3000万行以上的代码,参与过开发的有数千人,代码checkout出来有大约5个GB,而且bug特别多,open的有上千,即使最高优先级的s ...
- 如何在代码中减少if else语句的使用
前言 代码中嵌套的if/else结构往往导致代码不美观,也不易于理解.面向过程的开发中代码有大量的if else,在java中可以用一些设计模式替换掉这些逻辑,那么在js中是否也有类似的方法用来尽可能 ...
随机推荐
- SpringCore完整学习教程4,入门级别
本章从第4章开始 4. Logging Spring Boot使用Commons Logging进行所有内部日志记录,但保留底层日志实现开放.为Java Util Logging.Log4J2和Log ...
- raft算法的自我理解
1.raft算法是什么? 答:共识算法 2.raft算法有什么用? 答:维持不同机器的强一致性 3.raft算法通过什么方式来维持不同机器的强一致性? 答:传递log日志 ,按照官方的说法日志里面包含 ...
- C++ Qt开发:使用关联容器类
当我们谈论编程中的数据结构时,顺序容器是不可忽视的一个重要概念.顺序容器是一种能够按照元素添加的顺序来存储和检索数据的数据结构.它们提供了简单而直观的方式来组织和管理数据,为程序员提供了灵活性和性能的 ...
- django-celery-results - 使用 Django ORM/Cache 作为结果后端
https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html#django-celery-results-using-t ...
- Linux磁盘专题
物理磁盘名次和其作用 盘片:disk 盘片上下都有磁头. 磁盘面: 盘片有上下两面,每一面叫磁盘面 磁头:heads 每个磁头负责一个磁盘面,负责读取数据.将数据写入磁道. 磁头都是固定在机械臂上(机 ...
- 【UniApp】-uni-app-处理项目输入数据(苹果计算器)
前言 上一篇文章完成了项目的基本布局,这一篇文章我们来处理一下项目的输入数据 项目的输入数据主要是通过按键来输入的,所以我们需要对按键进行处理 那么我们就来看一下 uni-app-处理项目输入数据 步 ...
- 为什么许多数字孪生产品开始了GIS融合的尝试?
随着数字孪生技术的发展,越来越多的产品意识到要实现数字孪生的最大价值,需要考虑多个维度的数据,包括空间信息.地理位置.环境条件等.因此,许多数字孪生产品开始了与GIS系统的融合尝试,以进一步提升其功能 ...
- 可视化大屏与GIS之间如何实现互补?
在当今数字化时代,可视化大屏和地理信息系统(GIS)是两个在不同领域发挥重要作用的技术.可视化大屏以其生动.直观的图表.图像和动画展示方式,为数据可视化和信息展示提供了强大的工具.而GIS则通过地理空 ...
- 分享两种Pulsar消息积压topic级别策略老化办法
本文分享自华为云社区<Pulsar消息积压topic级别策略老化的两种方案>,作者: 张俭. Pulsar像大多数消息中间件一样,支持按时间和大小对消息积压进行老化.但是默认的策略只能在n ...
- Android移动、缩放和旋转手势实现
Android的部分图片编辑应用中需要对图片进行移动.缩放和旋转,这些变化都依赖于触摸手势实现,而本文主要阐述移动.缩放和旋转手势的简单实现. 一.移动 首先需要从触摸事件(MotionEvent)中 ...