1. 概述
在嵌入式系统开发过程中,代码的稳定性和可靠性至关重要。静态代码扫描工具作为一种自动化的代码质量检查手段,能够帮助开发者在编译前发现潜在的缺陷和错误,从而增强系统的稳定性。本文将介绍如何在嵌入式C/C++开发中使用静态代码扫描工具,并通过示例代码展示其应用效果。
文章目录
- 1. 概述
- 2. 静态代码扫描工具简介
- 3. 使用静态代码扫描工具的步骤
- 3.1 选择合适的静态代码扫描工具
- 3.2 配置扫描工具
- 3.3 集成到开发流程中
- 3.4 分析和修复问题
- 4. cppcheck功能演示
- 4.1 问题代码
- 4.2 问题1:数组越界
- 4.3 问题2:变量未初始化val
- 4.4 问题3:申请的内存未释放
- 4.5 修复后的代码
- 5. 总结
- 6. 高阶玩法
2. 静态代码扫描工具简介
静态代码扫描工具通过对源代码进行静态分析,检查代码中的语法错误、逻辑错误、安全漏洞等问题,并给出相应的警告或错误提示。这些工具可以集成到开发流程中,作为代码提交前的自动检查手段,帮助开发者及时发现问题并进行修复。
3. 使用静态代码扫描工具的步骤
3.1 选择合适的静态代码扫描工具
根据项目的需求和团队的偏好,选择一款适合嵌入式C/C++开发的静态代码扫描工具。常见的工具有Cppcheck、PCLint、Coverity等。
3.2 配置扫描工具
根据不同工具的文档,配置扫描规则、扫描范围等参数,确保工具能够针对项目的特点进行准确的扫描。
3.3 集成到开发流程中
将静态代码扫描工具集成到开发流程中,如持续集成/持续部署(CI/CD)系统中,实现自动化的代码质量检查。这个话题并非本文的核心,后面将使用单独文章来进行讨论。
3.4 分析和修复问题
定期运行扫描工具,分析扫描结果,并根据提示修复代码中的问题。
4. cppcheck功能演示
本文以cppeheck
(Ubuntu上的安装命令:sudo apt-get install cppcheck
)为例演示静态代码扫描的过程和结果展示。其它工具留待大家自行实验以比较各自的优缺点。
4.1 问题代码
#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;
}
如上所示,代码包含了三个函数:exampleFunction
、exampleFunction1
和 exampleFunction2
,以及一个 main
函数来调用它们。
下面是该程序编译运行的结果:
可以发现,该程序运行过程中出现了异常,被终止运行了。
实际上,这3个函数每一个函数都有其独特的问题,这些问题就可以通过静态代码扫描工具在编译前发现。下面我将逐一解释每个函数的问题,并展示cppcheck
扫描到的结果。
4.2 问题1:数组越界
exampleFunction
这个函数试图访问数组 array
的第 11
个元素(索引为 10
),但是 array
实际上只有 10
个元素(索引从 0
到 9
)。这是一个典型的数组越界访问错误。
使用cppcheck静态代码扫描工具进行扫描,会得到类似以下的扫描结果:
结果清晰命令:[example.c:11]: (error) Array 'array[10]' accessed at index 10, which is out of bounds.
,翻译过来就是:example.c的11行存在错误:数组array试图访问下标为10的数组元素,这超出了数组的边界
。
根据扫描结果的提示,我们应该修复数组越界访问的问题。修复方案如下:
- 确保数组索引在有效范围内。
- 如果需要访问数组的最后一个元素,使用
sizeof(array) / sizeof(array[0]) - 1
来计算最后一个有效索引。
4.3 问题2:变量未初始化val
exampleFunction1
这个函数声明了一个整数变量value
,但没有初始化它。然后它检查value
是否等于 0,但由于value
是未初始化的,这个检查是未定义的,因为它可能包含任何随机值,也就是每次运行可能会进入if分支,也可能会引入else分支,完全是一个随机事件。
使用cppcheck静态代码扫描工具进行扫描,会得到类似以下的扫描结果:
结果清晰命令:[example.c:24]: (error) Uninitialized variable: value
,翻译过来就是:example.c的24行存在错误:未初始化变量:value
。
根据扫描结果的提示,我们应该修复变量未初始化的问题。修复方案如下:
- 在定义所有变量时,确保对其进行初始化。
4.4 问题3:申请的内存未释放
exampleFunction2
这个函数分配了10
字节的内存给buffer
,然后尝试将字符串 "haha"
(包括一个终止符 ‘\0’ 总共 5 个字符)复制到buffer
中。函数在退出前没有释放分配的内存,导致出现了内存泄漏。
使用cppcheck静态代码扫描工具进行扫描,会得到类似以下的扫描结果:
结果清晰命令:[example.c:46]: (error) Memory leak: buffer
,翻译过来就是:example.c的46行存在错误:内存泄漏:value
。
根据扫描结果的提示,我们应该修复变量未初始化的问题。修复方案如下:
- 确保在不再需要内存时使用
free
函数释放它。
4.5 修复后的代码
#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
检测,也没有内存泄漏了。
5. 总结
静态代码扫描工具是嵌入式C/C++开发中不可或缺的一部分,它能够帮助开发者及时发现并修复代码中的潜在问题,提高系统的稳定性和可靠性。
6. 高阶玩法
通过集成静态代码扫描工具到开发流程中,并定期对代码进行扫描和修复,我们可以有效地提升嵌入式系统的质量。