在使用C/C++test进行静态分析、单元测试或者覆盖率检查的时候,很多报错其实不是规则本身的问题,而是因为编译器配置没有对上,C/C++test编译器配置怎么选择,以及C/C++test编译器配置不匹配为什么会报错,这两个问题是要放在一起看的,因为C/C++项目和普通的脚本文件不一样,不能拿来直接就分析。C/C++test在运行分析以前,需要把具体的C/C++编译器及其版本配置好,这个配置也应该尽量去反映项目实际构建时用的原始编译器。
一、C/C++test编译器配置怎么选择
编译器配置的时候不能凭感觉去选,不能因为看到项目里有gcc就随手选一个GCC,比较稳妥的做法是,先把项目真实的构建环境确认好,然后再在C/C++test里面把对应的compiler family、版本和执行文件选上。
1、先看项目实际用的编译器
我们要先进入到项目构建脚本、Makefile、CMake配置或者IDE工程设置里面,把实际调用的编译器确认一下,比如看它是gcc、g++、clang、cl.exe,还是ARM、IAR、Green Hills、QNX、Renesas、TI这些交叉编译器,因为C/C++test支持很多个编译器系列,里面包括GCC、Clang、Microsoft Visual C++、ARM、IAR、Green Hills、QNX GCC等,在选择的时候,应该尽量去贴近项目真实的工具链。
如果项目是桌面软件,常见的情况就是GCC、Clang或者MSVC;如果项目是嵌入式,那就更多地要看芯片平台和工具链的来源,比如ARM Compiler、IAR、GHS或者TI编译器,在这个地方,不能只看源码后缀是.c还是.cpp,关键是要看实际构建的时候到底是谁在编译它。
2、版本尽量不要差太多
在C/C++test里面选择编译器的时候,版本也是个很重要的事情,比如项目实际用的是GCC 9,但是配置成了GCC 4.9,这样的话,很多语法、内置宏、默认标准和警告选项就会变得不一样,轻一点的情况是报告里面出现一堆解析告警,严重的时候,直接就会报编译命令无法识别、头文件找不到或者源码无法解析。
3、交叉编译器要注意自定义配置
嵌入式项目经常会碰到一种情况,就是编译器看起来很像GCC,但实际是厂商自己改过的工具链,比如arm-none-eabi-gcc、powerpc-eabi-gcc,或者是某个RTOS、芯片SDK里面自带的编译器,这个时候,不能只按照“它长得像GCC”来对待,还要把它的参数、内置宏、目标架构和链接方式看一看。
二、C/C++test编译器配置不匹配为什么会报错
C/C++test的分析过程,并不是简单地把源码文本读取一遍,它要尽量把真实的编译环境复现出来,所以当编译器配置不匹配的时候,问题就会从预处理阶段开始,一路传到静态分析、单元测试和报告结果里面去。
1、宏定义和头文件路径会对不上
很多C/C++项目的代码能不能被工具“看懂”,是由宏定义和include路径决定的,比如在同一段代码里,可能会有:
#ifdef __GNUC__
#define INLINE inline
#endif
如果项目实际使用的是GCC,但是C/C++test被配成了MSVC,那么GNUC这些宏就不会按照预期去生效,相反地,如果实际是MSVC项目,却被配置成了GCC,也可能会遇到MSVC特有的关键字、属性或者头文件无法识别的情况。
2、编译选项不兼容会导致解析失败
不同的编译器,它们的参数风格是不一样的,GCC常见的是-D、-I、-std=c++17,而MSVC常见的则是/D/I/std:c++17,有些嵌入式编译器还会有自己专门用的内存段、对齐、优化、CPU架构或者浮点ABI参数。
3、目标平台差异会影响数据类型判断
在嵌入式项目里,编译器配置不匹配的话,还会对数据模型产生影响,比如int、long、指针宽度、结构体对齐方式、大小端、浮点支持,这些都可能和目标平台有关系,在静态分析规则判断溢出、类型转换、内存访问边界的时候,是需要依赖这些信息的。
三、编译器配置报错怎么排查
在遇到编译器配置相关的报错时,不要一上来就把规则集给改了,因为规则集只决定去检查什么,而编译器配置才决定工具能不能把代码正确理解了,排查的时候,可以按照“真实构建是否正常、构建信息是否完整、工具链是否匹配”这条线索往下看。
1、先确认原项目能正常编译
我们要先离开C/C++test,直接用项目原本的方法去构建一次,比如Makefile项目就跑一下make clean all,CMake项目就跑一下正常的生成和构建命令,Visual Studio项目就在VS里面确认能完整编译,如果原项目本身就编不过,在C/C++test里面继续分析通常只会让问题变大。
如果原项目能正常编译,再检查一下C/C++test有没有拿到同一套编译命令,在命令行分析的时候,可以用类似的方式把compiler指定好:
cpptestcli
-config"builtin://Recommended Rules"
-compiler gcc_9-64
-input cpptest.bdf
也可以在配置文件中写入:
cpptest.compiler.family=gcc_9-64
C/C++test支持用-compiler把编译器配置标识指定出来,也可以通过cpptest.compiler.family把同类信息配置好。
2、检查BDF或编译数据库是否完整
如果使用了BDF、trace构建或者编译数据库,重点是要看里面有没有真实的编译命令、工作目录、源文件、include路径和宏定义,很多报错不是因为compiler family选错了,而是因为构建信息不完整,比如只记录到了部分的.c文件,没有记录到SDK目录,或者是工作目录变了,导致相对路径全部失效了。
3、特殊工具链就建立自定义编译器
如果项目使用的是改造过的GCC、厂商的交叉编译器,或者linker需要固定的参数和特定的runtime library,这时候就不要硬去套用默认的配置了,可以在【File】→【New】→【Other】里面把C++test Custom Compiler创建出来,或者把自定义的compiler配置文件放到指定的目录中去管理,在新版的命令行场景下,也可以通过cpptest.compiler.dir.user把用户自定义的编译器配置目录指定出来。
总结
关于C/C++test编译器配置的选择,最关键的一句话就是:要按照项目真实的构建环境来选,不能看着源码表面特征去猜,实际使用的是GCC就选对应的GCC系列和版本,实际使用的是MSVC就选对应的Visual C++配置,嵌入式的交叉编译器则要看工具链能不能直接匹配上,不能匹配的时候就把自定义编译器配置做出来。