【C/C++ 静态代码检查工具 Cppcheck 】Cppcheck 检测器列表和检查规则大全一览

简介: 【C/C++ 静态代码检查工具 Cppcheck 】Cppcheck 检测器列表和检查规则大全一览

开启/关闭检查器

Cppcheck允许你通过命令行参数来启用或禁用特定的检查器。你可以使用--enable=参数来启用特定的检查器,或者使用--disable=参数来禁用特定的检查器。

例如,如果你只想启用内存相关的检查,你可以使用以下命令:

cppcheck --enable=warning,performance,portability,information,missingInclude --suppress=missingIncludeSystem yourfile.cpp

这个命令将启用所有的警告,性能,可移植性,信息和缺失包含的检查,但是会抑制系统缺失包含的警告。

你可以在Cppcheck的官方手册中找到更多关于如何使用这些参数的信息。手册中详细介绍了每个参数的用途和如何使用它们。

请注意,你需要根据你的需求来选择启用或禁用哪些检查器。不是所有的检查器都适合所有的情况,所以你需要根据你的代码和你想要检查的问题来选择合适的检查器。

内存泄漏相关检查

Cppcheck的内存泄漏检查是默认启用的,并且不能被单独禁用。

如果你想要检查内存泄漏,你应该使用–enable=all或者不使用–enable参数,因为内存泄漏检查是默认启用的。

性能相关

Cppcheck可以检查一些性能相关的问题。你可以通过以下命令来启用性能相关的检查:

cppcheck --enable=performance yourfile.cpp

这个命令将启用性能相关的检查,不会启用其他的检查器。

性能检查可以帮助你找到可能影响代码运行效率的问题,例如未使用的变量,未使用的函数返回值,以及可能导致性能下降的编程模式等。

然而,请注意,虽然Cppcheck可以帮助你找到一些性能问题,但它并不能替代完整的性能分析。如果你需要进行深入的性能分析,你可能需要使用专门的性能分析工具,如gprof,Valgrind的Callgrind,或者Intel VTune等。

你可以在Cppcheck的官方手册中找到更多关于如何使用这些参数的信息。

默认检查器

Cppcheck的默认检查器包括:

  • 错误(error):这类检查器主要检测可能导致程序崩溃或者运行不正常的问题,例如内存泄漏、数组越界、未初始化的变量等。
  • 警告(warning):这类检查器主要检测可能导致程序表现不如预期的问题,但不一定会导致程序崩溃,例如未使用的函数、未使用的变量等。

这两类检查器是默认启用的,无法被关闭。

对于其他的检查器,如样式(style)、性能(performance)、可移植性(portability)等,你可以通过--enable参数来启用或禁用。例如,如果你想启用所有的检查器,你可以使用--enable=all参数。

其他

  1. 预定义的输出格式:Cppcheck支持多种预定义的输出格式,包括与Visual Studio和gcc兼容的输出。你可以使用--template=vs--template=gcc选项来获取这些格式的输出。
  2. 用户定义的输出格式:你可以创建自己的输出格式。例如,你可以使用--template="{file}:{line}: {severity}: {message}"来创建类似于传统gcc的警告消息格式。
  3. 插件:Cppcheck支持使用插件来进行额外的检查。例如,misra.py插件可以用来验证代码是否符合MISRA C 2012标准,这是一套为嵌入式系统开发的代码规范。你可以使用--addon=misra.py选项来启用这个插件。
  4. 库配置:当使用外部库(如WinAPI、POSIX、gtk、Qt等)时,Cppcheck可能不知道这些外部函数的行为。你可以使用.cfg文件来配置这些库的行为,以帮助Cppcheck更准确地检测问题。
  5. HTML报告:你可以将Cppcheck的XML输出转换为HTML报告。你需要Python和pygments模块来实现这个功能。在Cppcheck的源代码树中,有一个名为htmlreport的文件夹,其中包含一个可以将Cppcheck的XML文件转换为HTML输出的脚本。
  6. 检查级别:Cppcheck有两种检查级别:正常和详尽。正常级别的检查是默认的,旨在在“合理”的时间内提供有效的检查。详尽级别的检查可以通过--check-level=exhaustive选项启用,这种检查级别在你可以等待结果的情况下可能会更有用。
  7. 加速分析:有一些方法可以加速Cppcheck的分析,例如限制预处理器配置,限制ValueFlow的最大if计数,或者在GUI中使用各种选项来限制分析。

检查器

你可以通过命令行应用程序获取所有已实现的检查器的当前列表:

# 获取检查器列表
cppcheck --doc
# 获取错误消息列表
cppcheck --errorlist

检查器列表

64位可移植性

检查是否存在64位可移植性问题:

  • 将地址分配给/从int/long
  • 在函数返回时从/到整数转换地址

断言

如果在断言语句中有副作用,发出警告(因为这会在debug/release构建中导致不同的行为)。

自动变量

指向变量的指针只有在变量在作用域内时才有效。检查:

  • 返回指向自动或临时变量的指针
  • 将变量的地址分配给函数的有效参数
  • 返回对本地/临时变量的引用
  • 返回函数参数的地址
  • 指针参数的可疑赋值
  • 函数参数的无用赋值

布尔值

布尔类型检查:

  • 对布尔值进行递增
  • 将布尔表达式与0或1以外的整数进行比较
  • 使用关系运算符比较返回布尔值的函数
  • 使用关系运算符比较布尔值与布尔值
  • 在位表达式中使用布尔值
  • 在条件中进行指针加法(要么忘记解引用,要么需要指针溢出才能使条件为假)
  • 将布尔值赋给指针或浮点数
  • 从返回布尔值的函数返回非0或1的整数

Boost使用

检查Boost的无效使用:

  • 在BOOST_FOREACH中修改容器

边界检查

越界检查:

  • 数组索引越界
  • 指针算术溢出
  • 缓冲区溢出
  • strncat()的危险使用
  • 在检查之前使用数组索引
  • 部分字符串写入导致缓冲区未终止为零。
  • 检查是否将足够大的数组传递给函数
  • 以负大小分配内存

检查函数使用

检查函数使用:

  • 非void函数中缺少’return’
  • 没有使用某些函数的返回值
  • 函数的无效输入值
  • 如果调用了某个使用被禁止的函数,发出警告
  • memset()的第三个参数为零
  • memset()的第二个参数超出范围
  • memset()的第二个参数为浮点数
  • 对返回值进行std::move的复制消除优化
  • 使用memcpy()/memset()代替for循环

检查每个类的代码:

  • 缺少构造函数和复制构造函数
  • 应该是显式的构造函数
  • 所有变量都由构造函数初始化了吗?
  • 所有变量都由’operator='分配了吗?
  • 如果在类上使用了memset, memcpy等,发出警告
  • 如果为类分配了内存,是否使用了malloc()
  • 如果它是一个基类,检查析构函数是否是虚函数
  • 是否有未使用的私有函数?
  • 'operator='应检查自我赋值
  • 成员函数的常量性
  • 初始化的顺序
  • 建议使用初始化列表
  • 用自身初始化成员
  • 对’this’的可疑减法
  • 在构造函数/析构函数中调用纯虚函数
  • 重复的继承数据成员
  • 检查任意使用公共接口是否会导致零除错误
  • 删除“自我指针”然后访问’this’
  • 检查在覆盖虚函数时是否使用了’override’关键字
  • 检查’一次定义规则’是否被违反

条件

匹配赋值和其他条件的条件:

  • 不匹配的赋值和比较 => 比较总是真/假
  • 比较中的lhs和rhs不匹配 => 比较总是真/假
  • 检测使用|应该使用&的情况
  • 重复的条件和赋值
  • 检测匹配的’if’和’else if’条件
  • 不匹配的位与(a &= 0xf0; a &= 1; => a = 0)
  • 内部条件的相反总是假
  • 早期退出后的相同条件总是假
  • 总是真/假的条件
  • 通过||的互斥总是评估为真
  • 总是真/假的模结果的比较。
  • 已知的变量值 => 条件总是真/假
  • 无效的溢出测试。一些主流编译器在优化代码时会删除这样的溢出测试。
  • 在条件中可疑地分配容器/迭代器 => 条件总是真。

异常安全性

检查异常安全性:

  • 在析构函数中抛出异常
  • 在无效状态下抛出异常
  • 抛出捕获的异常的副本,而不是重新抛出原始异常
  • 通过值而不是引用捕获异常
  • 在noexcept, nothrow(), attribute((nothrow))或__declspec(nothrow)函数中抛出异常
  • 调用函数foo()时未处理的异常规范
  • 没有当前处理的异常就重新抛出

使用格式字符串的IO

检查使用格式字符串的输入/输出操作。

  • 'sprintf’函数的错误使用(数据重叠)
  • 'scanf’格式字符串中缺失或错误的宽度说明符
  • 使用已关闭的文件
  • 不定位的文件输入/输出导致

未定义的行为

  • 读取只打开用于写入的文件(反之亦然)
  • 在附加模式下打开的文件上进行重新定位操作
  • 同一文件不能在不同的流上同时打开用于读写
  • 在输入流上使用fflush()
  • 输出流的无效使用。例如:‘std::cout << std::cout;’
  • 给’printf’或’scanf’提供的参数数量错误

泄漏(自动变量)

检测当一个自动变量被分配但未被释放或被释放两次时。

内存泄漏(地址未被获取)

没有获取分配内存的地址

内存泄漏(类变量)

如果构造函数分配内存,那么析构函数必须释放它。

内存泄漏(函数变量)

当函数超出范围时是否有任何分配的内存

内存泄漏(结构体成员)

不要忘记释放结构体成员

空指针

空指针

  • 空指针解引用
  • 未定义的空指针算术

这是Cppcheck检查器列表的第二部分内容:

其他

其他检查:

  • 零除
  • 构造后立即销毁的作用域对象
  • 断言语句中的赋值
  • 释放无效内存位置的free()或delete
  • 与负右操作数的位操作
  • 将getc(),fgetc()和getchar()的返回值转换为字符并与EOF比较
  • 在InterlockedDecrement()调用后非交错访问的竞态条件
  • 表达式’x = x++;'依赖于副作用的求值顺序
  • 联合的重叠写入
  • 要么除以零,要么无用的条件
  • 移动或转发变量的访问。
  • const变量的冗余数据复制
  • 变量或缓冲区的后续赋值或复制
  • 通过值传递参数
  • 将NULL指针传递给具有可变参数数量的函数会导致UB。
  • C++代码中的C风格指针转换
  • 在不兼容的指针类型之间进行转换
  • 不完整的语句
  • 检查如何使用有符号字符变量
  • 变量的范围可以限制
  • 不寻常的指针算术。例如:“abc” + ‘d’
  • 在switch语句中的冗余赋值,增量,或位操作
  • 在switch语句中的冗余strcpy
  • switch()中可疑的case标签
  • 变量的赋值

STL使用

检查STL的无效使用:

  • 越界错误
  • 在迭代遍历容器时误用迭代器
  • 在调用中的容器不匹配
  • 在调用中的相同迭代器
  • 解引用已删除的迭代器
  • 对于向量:在使用push_back后使用迭代器/指针
  • 优化:使用empty()而不是size()来保证快速代码
  • 使用find时的可疑条件
  • 在关联容器中不必要的搜索
  • 冗余条件
  • 使用string::c_str()时的常见错误
  • string和STL函数的无用调用
  • 解引用无效的迭代器
  • 从空的STL容器中读取
  • 遍历空的STL容器
  • 考虑使用STL算法而不是原始循环
  • 使用互斥锁时的错误锁定

Sizeof

sizeof()使用检查

  • 数组作为函数参数的sizeof
  • 数值作为函数参数的sizeof
  • 使用sizeof(pointer)而不是指向的数据的大小
  • 查找’sizeof sizeof …’
  • 查找sizeof()内的计算
  • 查找sizeof()内的函数调用
  • 查找sizeof()的可疑计算
  • 使用’sizeof(void)',这是未定义的

字符串

检测C风格字符串的误用:

  • 将源和目标传递给sprintf的重叠缓冲区
  • 'substr’和’strncmp’的长度参数错误
  • 可疑条件(字符串字面量的运行时比较)
  • 可疑条件(字符串/字符字面量作为布尔值)
  • 与char*变量的字符串字面量的可疑比较
  • 与char*变量的’\0’的可疑比较
  • 重叠的strcmp()表达式

类型

类型检查

  • 位移过多的位(只有在使用–platform时启用)
  • 有符号整数溢出(只有在使用–platform时启用)
  • 危险的符号转换,当有符号值可以为负时
  • 当将int结果分配给long变量时可能丢失信息
  • 当将int结果作为long返回值返回时可能丢失信息
  • 浮点转换溢出

未初始化的变量

未初始化的变量

  • 使用未初始化的局部变量
  • 使用分配的数据在初始化之前

未使用的函数

检查从未调用的函数

UnusedVar

UnusedVar检查

  • 未使用的变量
  • 分配但未使用的变量
  • 未读的变量
  • 未分配的变量
  • 未使用的结构成员

使用后缀运算符

如果使用后缀运算符++或–而不是前缀运算符,发出警告

Vaarg

检查变量参数列表的误用:

  • 向va_start()传递错误的参数
  • 将引用传递给va_start()
  • 缺少va_end()
  • 在打开之前使用va_list
  • 对va_start/va_copy()的后续调用

这是Cppcheck检查器列表的全部内容。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
1月前
|
存储 编译器 C++
【C++】深入探索类和对象:初始化列表及其static成员与友元(一)
【C++】深入探索类和对象:初始化列表及其static成员与友元
|
6月前
|
Java Linux C++
性能工具之 C/C++ 分析工具 valgrind
【5月更文挑战第26天】性能工具之 C/C++ 分析工具 valgrind
116 2
性能工具之 C/C++ 分析工具 valgrind
|
5月前
|
编译器 C语言 C++
C++一分钟之-C++11新特性:初始化列表
【6月更文挑战第21天】C++11的初始化列表增强语言表现力,简化对象构造,特别是在处理容器和数组时。它允许直接初始化成员变量,提升代码清晰度和性能。使用时要注意无默认构造函数可能导致编译错误,成员初始化顺序应与声明顺序一致,且在重载构造函数时避免歧义。利用编译器警告能帮助避免陷阱。初始化列表是高效编程的关键,但需谨慎使用。
68 2
|
2月前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
73 30
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
29 3
|
1月前
|
编译器 C++
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
45 3
|
1月前
|
C++
【C++】深入探索类和对象:初始化列表及其static成员与友元(二)
【C++】深入探索类和对象:初始化列表及其static成员与友元
|
1月前
|
编译器 C++
【C++】深入探索类和对象:初始化列表及其static成员与友元(三)
【C++】深入探索类和对象:初始化列表及其static成员与友元
|
2月前
|
C++
HTML+JavaScript构建一个将C/C++定义的ANSI字符串转换为MASM32定义的DWUniCode字符串的工具
HTML+JavaScript构建一个将C/C++定义的ANSI字符串转换为MASM32定义的DWUniCode字符串的工具
|
4月前
|
Rust 测试技术 编译器
Rust与C++的区别及使用问题之Rust项目中组织目录结构的问题如何解决
Rust与C++的区别及使用问题之Rust项目中组织目录结构的问题如何解决