开启/关闭检查器
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
参数。
其他
- 预定义的输出格式:Cppcheck支持多种预定义的输出格式,包括与Visual Studio和gcc兼容的输出。你可以使用
--template=vs
或--template=gcc
选项来获取这些格式的输出。 - 用户定义的输出格式:你可以创建自己的输出格式。例如,你可以使用
--template="{file}:{line}: {severity}: {message}"
来创建类似于传统gcc的警告消息格式。 - 插件:Cppcheck支持使用插件来进行额外的检查。例如,
misra.py
插件可以用来验证代码是否符合MISRA C 2012标准,这是一套为嵌入式系统开发的代码规范。你可以使用--addon=misra.py
选项来启用这个插件。 - 库配置:当使用外部库(如WinAPI、POSIX、gtk、Qt等)时,Cppcheck可能不知道这些外部函数的行为。你可以使用.cfg文件来配置这些库的行为,以帮助Cppcheck更准确地检测问题。
- HTML报告:你可以将Cppcheck的XML输出转换为HTML报告。你需要Python和pygments模块来实现这个功能。在Cppcheck的源代码树中,有一个名为htmlreport的文件夹,其中包含一个可以将Cppcheck的XML文件转换为HTML输出的脚本。
- 检查级别:Cppcheck有两种检查级别:正常和详尽。正常级别的检查是默认的,旨在在“合理”的时间内提供有效的检查。详尽级别的检查可以通过
--check-level=exhaustive
选项启用,这种检查级别在你可以等待结果的情况下可能会更有用。 - 加速分析:有一些方法可以加速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检查器列表的全部内容。
结语
在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。
这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。
我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。