【工具推荐】CppCheck:C/C++静态代码检测工具的使用教程和实战案例,提前发现隐患,让你的代码更安全

CppCheck 是一款强大的静态代码分析工具,可以大幅提升C/C++代码的质量和安全性。通过安装和使用CppCheck,可以提前发现潜在的问题,减少Bug和漏洞的产生。同时,将CppCheck集成到CI/CD管道中,可以实现持续化代码检测,确保代码库始终保持高质量。希望本文对你了解和使用CppCheck有所帮助,让你的代码更加安全可靠。

在这里插入图片描述


🧑 博主简介:现任阿里巴巴嵌入式技术专家,15年工作经验,深耕嵌入式+人工智能领域,精通嵌入式领域开发、技术管理、简历招聘面试。CSDN优质创作者,提供产品测评、学习辅导、简历面试辅导、毕设辅导、项目开发、C/C++/Java/Python/Linux/AI等方面的服务,如有需要请站内私信或者联系任意文章底部的的VX名片(ID:gylzbk

💬 博主粉丝群介绍:① 群内初中生、高中生、本科生、研究生、博士生遍布,可互相学习,交流困惑。② 热榜top10的常客也在群里,也有数不清的万粉大佬,可以交流写作技巧,上榜经验,涨粉秘籍。③ 群内也有职场精英,大厂大佬,可交流技术、面试、找工作的经验。④ 进群免费赠送写作秘籍一份,助你由写作小白晋升为创作大佬。⑤ 进群赠送CSDN评论防封脚本,送真活跃粉丝,助你提升文章热度。有兴趣的加文末联系方式,备注自己的CSDN昵称,拉你进群,互相学习共同进步。

在这里插入图片描述

概述

在软件开发过程中,代码的质量和安全性是至关重要的。特别是在C和C++这样容易出现低级错误的语言中,代码静态分析工具如CppCheck显得尤为重要。CppCheck是一款开源的C/C++静态代码分析工具,它能帮助开发者查找潜在的问题,确保代码的质量和安全性。本文将介绍CppCheck的功能、安装、使用方法及其在提升代码质量方面的重要作用。

什么是CppCheck?

CppCheck是一款专门针对C和C++代码的静态分析工具。与传统编译器不同,CppCheck可以捕获到更多潜在的错误和编码不规范之处,通过对代码进行深入分析,帮助开发者识别出:

  • 潜在的内存泄漏
  • 未初始化的变量
  • 可疑的逻辑错误
  • 代码中的资源泄漏
  • 非常见的编程错误与漏洞

安装CppCheck

CppCheck支持多个平台(Windows、Linux、macOS)。这里分别介绍在不同平台上的安装方法。

Windows

在Windows上,可以通过安装程序安装CppCheck。

  1. 访问 CppCheck的官方网站
  2. 下载适合Windows的安装包(通常以.exe结尾)。
  3. 双击安装包,按照提示完成安装。

Linux

在大多数Linux发行版上,可以直接通过包管理器安装CppCheck。

Debian/Ubuntu

sudo apt-get update
sudo apt-get install cppcheck

CentOS/Fedora

sudo yum install cppcheck

其他发行版

可以通过源码安装,具体步骤如下:

git clone https://github.com/danmar/cppcheck.git
cd cppcheck
make SRCDIR=build CFGDIR=cfg HAVE_RULES=yes
sudo make install

macOS

在macOS上,可以使用Homebrew安装CppCheck。

brew install cppcheck

CppCheck使用教程

安装完成后,就可以使用CppCheck对C/C++代码进行静态分析了。

基本用法

最基本的用法是对单个源文件进行检测:

cppcheck example.c

对整个项目目录进行检测:

cppcheck /path/to/project

生成HTML格式的报告

CppCheck可以生成HTML格式的报告,更方便查看和分享。

cppcheck --enable=all --xml --xml-version=2 . 2> result.xml
cppcheck-htmlreport --file=result.xml --report-dir=cppcheck-report --source-dir=.

常用选项

  • --enable=all:启用所有类型的检查,包括样式、性能、可移植性等。
  • --inconclusive:显示可能存在但不确定的问题。
  • --force:对所有文件进行检查,即使它们通常不会单独编译。
  • --std=c++11:指定C++标准(例如:C++11、C++14、C++17)。
  • --suppressions-list=suppressions.txt:指定一个文件列表,包含要忽略的特定检测规则。

示例

对一个包含多个文件的项目进行全面检查:

cppcheck --enable=all --inconclusive --std=c++11 /path/to/project

如果你想忽略某些特定的警告,可以使用 --suppress 选项。假设你想忽略所有关于未使用变量的警告:

cppcheck --enable=all --suppress=unusedVariable /path/to/project

集成到CI/CD管道

CppCheck可以很好地集成到CI/CD管道中,例如Jenkins、GitLab CI等。这确保在每次代码提交和合并请求时,自动执行代码静态分析,提前发现问题。

GitLab CI 示例

在GitLab CI中,可以编写一个简单的.gitlab-ci.yml文件:

stages:
  - analyze

cppcheck:
  stage: analyze
  script:
    - cppcheck --enable=all --xml --xml-version=2 . 2> cppcheck.xml
  artifacts:
    reports:
      codequality: cppcheck.xml

cppcheck实战演示

问题代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int exampleFunction()
{
	printf("%s enter\n", __func__);

	int array[10];
	int index = 10;
	array[index] = 42; // 数组越界访问

	printf("%s leave\n", __func__);

	return 0;
}

int exampleFunction1()
{
	int value; // 变量未初始化

	printf("%s enter\n", __func__);

	if (value == 0) {
			printf("value is 0");
	} else {
			printf("value not 0");
	}

	printf("%s leave\n", __func__);

	return 0;
}

int exampleFunction2()
{
	char *buffer = NULL;

	printf("%s enter\n", __func__);

	buffer = malloc(10); //buffer申请的内存没有释放
	strcpy(buffer, "haha");

	printf("%s leave\n", __func__);

	return 0;
}

int main(int argc, char *argv[])
{
	exampleFunction();
	exampleFunction1();
	exampleFunction2();
	
	return 0;
}

如上所示,代码包含了三个函数:exampleFunctionexampleFunction1exampleFunction2,以及一个 main函数来调用它们。
下面是该程序编译运行的结果:
在这里插入图片描述
可以发现,该程序运行过程中出现了异常,被终止运行了。

实际上,这3个函数每一个函数都有其独特的问题,这些问题就可以通过静态代码扫描工具在编译前发现。下面我将逐一解释每个函数的问题,并展示cppcheck扫描到的结果。

问题1:数组越界

exampleFunction这个函数试图访问数组 array 的第 11 个元素(索引为 10),但是 array 实际上只有 10 个元素(索引从 09)。这是一个典型的数组越界访问错误

使用cppcheck静态代码扫描工具进行扫描,会得到类似以下的扫描结果:
在这里插入图片描述
结果清晰命令:[example.c:11]: (error) Array 'array[10]' accessed at index 10, which is out of bounds.,翻译过来就是:example.c的11行存在错误:数组array试图访问下标为10的数组元素,这超出了数组的边界

根据扫描结果的提示,我们应该修复数组越界访问的问题。修复方案如下:

  1. 确保数组索引在有效范围内。
  2. 如果需要访问数组的最后一个元素,使用sizeof(array) / sizeof(array[0]) - 1来计算最后一个有效索引。

问题2:变量未初始化val

exampleFunction1这个函数声明了一个整数变量value,但没有初始化它。然后它检查value是否等于 0,但由于value是未初始化的,这个检查是未定义的,因为它可能包含任何随机值,也就是每次运行可能会进入if分支,也可能会引入else分支,完全是一个随机事件。

使用cppcheck静态代码扫描工具进行扫描,会得到类似以下的扫描结果:
在这里插入图片描述
结果清晰命令:[example.c:24]: (error) Uninitialized variable: value,翻译过来就是:example.c的24行存在错误:未初始化变量:value

根据扫描结果的提示,我们应该修复变量未初始化的问题。修复方案如下:

  1. 在定义所有变量时,确保对其进行初始化。

问题3:申请的内存未释放

exampleFunction2这个函数分配了10字节的内存给buffer,然后尝试将字符串 "haha"(包括一个终止符 ‘\0’ 总共 5 个字符)复制到buffer中。函数在退出前没有释放分配的内存,导致出现了内存泄漏。

使用cppcheck静态代码扫描工具进行扫描,会得到类似以下的扫描结果:
在这里插入图片描述
结果清晰命令:[example.c:46]: (error) Memory leak: buffer,翻译过来就是:example.c的46行存在错误:内存泄漏:value

根据扫描结果的提示,我们应该修复变量未初始化的问题。修复方案如下:

  1. 确保在不再需要内存时使用free函数释放它。

修复后的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int exampleFunction()
{
    printf("%s enter\n", __func__);

    int array[10];
    int index = sizeof(array) / sizeof(array[0]) - 1;
    array[index] = 42; // 数组越界访问,已经修复

    printf("%s leave\n", __func__);

    return 0;
}

int exampleFunction1()
{
    int value = 0; // 变量未初始化,已经修复

    printf("%s enter\n", __func__);

    if (value == 0) {
        printf("value is 0");
    } else {
        printf("value not 0");
    }

    printf("%s leave\n", __func__);

    return 0;
}

int exampleFunction2()
{
    char *buffer = NULL;

    printf("%s enter\n", __func__);

    buffer = malloc(10); //buffer申请的内存没有释放,已经修复
    strcpy(buffer, "haha");

    free(buffer);

    printf("%s leave\n", __func__);

    return 0;
}

int main(int argc, char *argv[])
{
    exampleFunction();
    exampleFunction1();
    exampleFunction2();

    return 0;
}

修复后的代码,编译运行结果如下所示,可以看到,所有函数正常执行,使用valgrind检测,也没有内存泄漏了。
在这里插入图片描述

结论

CppCheck 是一款强大的静态代码分析工具,可以大幅提升C/C++代码的质量和安全性。通过安装和使用CppCheck,可以提前发现潜在的问题,减少Bug和漏洞的产生。同时,将CppCheck集成到CI/CD管道中,可以实现持续化代码检测,确保代码库始终保持高质量。希望本文对你了解和使用CppCheck有所帮助,让你的代码更加安全可靠。如果有任何疑问或需要进一步