C&C++代码编译和分析工具探究
【Clang】
Clang是C、C++、Objective-C和Objective-C++编程语言,以及OpenMP、OpenCL、RenderScript、CUDA和HIP框架的编译器前端。它使用LLVM编译器基础设施作为后端,自LLVM 2.6以来,一直是LLVM发布周期的一部分。
它被设计为GNU编译器集(GCC)的替代者,支持大部分的编译标志和非官方语言扩展。它的贡献者包括苹果、微软、谷歌、ARM、索尼、英特尔和高级微设备(AMD)。它是一个开放源码软件,源代码是在伊利诺伊大学/NCSA许可证下发布的,这是一个自由软件许可证。从v9.0.0版本开始,它被重新授权到Apache License 2.0版本(LLVM除外)。
Clang项目包括Clang前端,一个静态分析器和几个代码分析工具。
【背景】
从2005年开始,苹果公司在许多商业产品中广泛使用了LLVM,其中包括iOS SDK和Xcode 3.1。
LLVM最初是使用GCC的前端的,但GCC结果给LLVM的开发者和Apple公司带来了一些问题。GCC源码对开发者来说是一个庞大且有些繁琐的系统;正如一位长期使用GCC的开发者在谈到LLVM时所说的那样,"试图让河马起舞其实并没有多少乐趣"。
苹果软件大量使用了Objective-C,但GCC中的Objective-C前端对于GCC开发者来说,其优先级较低。另外,GCC并不能顺利地集成到苹果公司的集成开发环境(IDE)中。最后,GCC是根据GNU General Public License (GPL) 版本3的条款授权的,它要求发布GCC的扩展或修改版本的开发者必须提供他们的源代码,而LLVM有一个类似BSD的许可证,没有这样的要求。
苹果公司选择从头开始开发一个新的编译器前端,支持C、Objective-C和C++。这个 "clang "项目是在2007年7月开源的。
【设计】
Clang的目的是在LLVM之上工作。Clang和LLVM的结合提供了大部分的工具链,可以替代完整的GCC堆栈。因为它是基于库的设计,就像LLVM的其他部分一样,Clang很容易嵌入到其他应用中。这也是大多数OpenCL实现都是用Clang和LLVM构建的原因之一。
Clang的主要目标之一是提供一个基于库的架构,并且让编译器与源代码交互的工具,如集成开发环境(IDE)图形用户界面(GUI)等更紧密地结合在一起。相比之下,GCC被设计成了编译-链接-调试的工作流程,要将其与其他工具集成起来并不容易。例如,GCC使用了一个叫做fold的步骤,这个步骤是整个编译过程的关键,它的副作用是将代码树翻译成与原始源码不同的形式。如果在fold步骤期间或之后发现了错误,要将其翻译回原始源码中的一个位置可能会很困难。另外,在IDE中使用GCC堆栈的厂商会使用单独的工具来索引代码,提供语法高亮和自动完成等功能。
与GCC相比,Clang的设计目的是在编译过程中保留更多的信息,并保留原始代码的整体形式。这样做的目的是为了更容易将错误映射回原始源码。Clang提供的错误报告也是为了更详细、更具体,以及机器可读,因此IDE可以在编译过程中对编译器的输出进行索引。编译器的模块化设计可以提供源代码索引、语法检查以及其他通常与快速应用程序开发系统相关的功能。解析树也更适合于支持自动代码重构,因为它直接表示原始源代码。
Clang只编译类似C的编程语言,如C、C++、Objective-C、Objective-C++、OpenCL、CUDA和HIP。对于其他语言,如Ada,LLVM仍然依赖于GCC或其他编译器前端。在很多情况下,Clang可以根据需要使用或换成GCC,对整个工具链没有其他影响。它支持大多数常用的GCC选项。Nvidia和The Portland Group的一个子项目Flang增加了对Fortran的支持。
【性能和GCC兼容性】
Clang的设计是为了与GCC高度兼容。Clang的命令行界面与GCC相似,并与GCC共享许多标志和选项。Clang实现了许多GNU语言的扩展,并在默认情况下启用了它们。Clang 实现了许多 GCC 编译器的内在性。例如,尽管 Clang 实现了与 C11 原子内含函数完全对应的原子内含函数,但为了与 GCC 和 libstdc++ 兼容,它也实现了 GCC 的 __sync_* 内含函数。Clang还保持了与GCC生成的对象代码的ABI兼容性。在实际应用中,Clang经常可以作为GCC的替代方案。
与GCC等竞争性编译器相比,Clang的开发者旨在减少内存占用,提高编译速度。2007年10月,他们报告说,Clang编译Carbon库的速度是GCC的两倍多,而使用的内存和磁盘空间只有GCC的六分之一。截至2014年中期,Clang赢得了超过三分之一的基准测试,其中GCC赢得了大部分。截至2014年,Clang编译的程序的性能落后于GCC编译的程序,有时性能差出很多(高达5.5倍),这也重复了之前关于性能变慢的报告。
2016年11月更多的比较表明,这两个编译器都为了提高性能而做了改进。截止到GCC 4.8.2与clang 3.4的对比,在大量的测试文件上,GCC在优化好的源码上性能优于clang约17%。测试结果是针对特定代码的,未经优化的C源代码则正好相反。这两个编译器在性能上现在看起来大致上是旗鼓相当的。
【特点和目标】
该项目的一些目标包括:
终端用户功能:
l 快速编译和低内存使用
l 表现力强的诊断(示例)
l GCC的兼容性
【使用案例】
Windows+vscode+clang
安装vscode, c/c++ 插件:
安装clang:
clang --version
C 代码实例: free.c
#include <stdio.h>
#include <stdlib.h>
void f(int *g) {
printf("%x", (int)g);
}
int main() {
int *p;
p = (int *)malloc(10 * sizeof(int));
f(p);
free(p);
return 0;
}
C++代码实例: helloworld.cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> msg {"Hello", "C++", "World", "from", "VS Code", "and the C++ extension!"};
for (const string& word : msg)
{
cout << word << " ";
}
cout << endl;
}
配置文件实例: tasks.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "clang build active file",
"command": "clang",
"args": [
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
列号和齿条诊断
clang生成的所有诊断程序都包括完整的列号信息。clang的命令行编译器驱动程序使用这些信息来打印 "点诊断"。(IDE可以使用这些信息来显示行内错误标记。) 它可以让你很容易理解某个特定代码中的问题所在。
这个点(绿色的"^"字符)准确地显示了问题所在,即使是在字符串内部。这让我们可以很容易地跳转到问题所在,并且在一行中出现多个相同字符的实例时也很有帮助。
$ clang -fsyntax-only format-strings.c
format-strings.c:91:13: warning: '.*' specified field precision is missing a matching 'int' argument
printf("%.*d");
^
请注意,现代版本的 GCC 跟随 Clang 的思路,现在能够给出一个诊断列,并在结果中包含一段源文本的片段。然而,Clang 的列号更准确,它指向的是有问题的格式指定器,而不是解析器在检测到问题时到达的 ) 字符。另外,Clang的诊断结果默认是彩色的,这样更容易与附近的文本区分开来。
相关文本的范围高亮显示
Clang可以捕捉并准确地跟踪你的程序中的表达式、语句和其他构造物的范围信息,并利用这些信息来进行诊断,以突出显示相关信息。在下面这个例子中,你可以不需要看原始的源代码,就可以根据Clang的错误来理解是什么地方出了问题。因为clang会打印出一个点,你可以清楚地知道它抱怨的是哪个加号。范围信息突出显示了加号的左右两边,问题出在哪里,一目了然。范围信息对于涉及优先级问题和许多其他情况下非常有用。
$ gcc-4.9 -fsyntax-only t.c
t.c: In function 'int f(int, int)':
t.c:7:39: error: invalid operands to binary + (have 'int' and 'struct A')
return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);
^
$ clang -fsyntax-only t.c
t.c:7:39: error: invalid operands to binary expression ('int' and 'struct A')
return y + func(y ? ((SomeA.X + 40) + SomeA) / 42 + SomeA.X : SomeA.X);
~~~~~~~~~~~~~~ ^ ~~~~~
精确的措辞
在下面的例子中,不仅告诉你*有问题,还准确地告诉你为什么,并告诉你类型是什么(如果是一个复杂的子表达式,比如调用一个超载函数)。这种对细节的关注使我们更容易理解和快速修复问题。
$ gcc-4.9 -fsyntax-only t.c
t.c:5:11: error: invalid type argument of unary '*' (have 'int')
return *SomeA.X;
^
$ clang -fsyntax-only t.c
t.c:5:11: error: indirection requires pointer operand ('int' invalid)
int y = *SomeA.X;
^~~~~~~~
类型预留
下面的例子说明了在C语言中保存一个类型定义是很重要的。
$ clang -fsyntax-only t.c
t.c:15:11: error: can't convert between vector values of different size ('__m128' and 'int const *')
myvec[1]/P;
~~~~~~~~^~
下面的例子显示了编译器在哪些地方可以揭示一个 类型定义的底层细节。如果用户对pid_t存有疑惑,clang会贴心的显示“aka”
$ clang -fsyntax-only t.c
t.c:13:9: error: member reference base type 'pid_t' (aka 'int') is not a structure or union
myvar = myvar.x;
~~~~~ ^
在C++中,类型保存包括保留写到类型名中的任何限定。比如说:
namespace services {
struct WebService { };
}
namespace myapp {
namespace servers {
struct Server { };
}
}
using namespace myapp;
void addHTTPService(servers::Server const &server, ::services::WebService const *http) {
server += http;
}
对上面代码进行编译,Clang既提供了准确的信息,又保留了用户所写的类型(例如,"servers:::Server"、"::services:::WebService")。
$ clang -fsyntax-only t.cpp
t.cpp:9:10: error: invalid operands to binary expression ('servers::Server const' and '::services::WebService const *')
server += http;
~~~~~~ ^ ~~~~
当然,类型保存也可以扩展到模板的使用,Clang保留了关于特定模板特殊化(如 std:::vector<Real>)在源代码中如何拼写的信息。比如说:
$ clang -fsyntax-only t.cpp
t.cpp:12:7: error: incompatible type assigning 'vector<Real>', expected 'std::string' (aka 'class std::basic_string<char>')
str = vec;
^ ~~~
修复提示
修复提示为修复源代码中的小问题提供了建议。当Clang产生了一个诊断结果时,如果它可以解决某个特定问题(例如,非标准或多余的语法、缺失的关键字、常见的错误等)。它会以代码转换的形式提供具体的指导,以纠正问题。在下面的例子中,Clang警告了一个自1993年以来被认为已经过时的GCC扩展的使用。应将下划线的代码去掉,然后替换成点行下面的代码(".x="或".y=")。
$ clang t.c
t.c:5:28: warning: use of GNU old-style field designator extension
struct point origin = { x: 0.0, y: 0.0 };
~~ ^
.x =
t.c:5:36: warning: use of GNU old-style field designator extension
struct point origin = { x: 0.0, y: 0.0 };
~~ ^
.y =
"修复 "提示对于解决用户常见的错误和误解是最有用的。例如,C++用户通常会忘记类模板显式化的语法,就像下面这个例子中的错误。同样,在描述了问题之后,Clang提供了修复方法—添加模板<>--作为诊断的一部分。
$ clang t.cpp
t.cpp:9:3: error: template specialization requires 'template<>'
struct iterator_traits<file_iterator> {
^
template<>
模板类型差异
模板类型会比较长,难于阅读。当出现在错误信息的一部分时,就更难理解了。Clang并不只是打印出类型名称,而是会突出显示不同之处。为了更清楚地显示模板结构,模板类型也可以以缩进式文本树的形式打印出来。
默认值:带偏移类型的模板差异
t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<[...], float>>' to 'vector<map<[...], double>>' for 1st argument;
-fno-elid-type: 模板差异,不含偏移。
t.cc:4:5: note: candidate function not viable: no known conversion from 'vector<map<int, float>>' to 'vector<map<int, double>>' for 1st argument;
-fdiagnostics-show-template-tree: 模板树连同偏移信息打印
t.cc:4:5: note: candidate function not viable: no known conversion for 1st argument;
vector<
map<
[...],
[float != double]>>
-fdiagnostics-show-template-tree -fno-elide-type: 模板树打印,无偏移信息
t.cc:4:5: note: candidate function not viable: no known conversion for 1st argument;
vector<
map<
int,
[float != double]>>
自动宏扩展
很多错误都是在宏中发生的,而这些错误有时是深度嵌套的。使用传统的编译器,你需要深入挖掘宏的定义,才能了解自己是如何陷入困境的。下面这个简单的例子展示了Clang是如何通过宏实例化时自动打印出实例化信息和嵌套范围信息来诊断的,并在一个更大的例子中展示了一些其他部分是如何工作的。
$ clang -fsyntax-only t.c
t.c:80:3: error: invalid operands to binary expression ('typeof(P)' (aka 'struct mystruct') and 'typeof(F)' (aka 'float'))
X = MYMAX(P, F);
^~~~~~~~~~~
t.c:76:94: note: expanded from:
#define MYMAX(A,B) __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
~~~ ^ ~~~
下面是另一个发生在 "window "Unix程序包(实现了 "wwopen "类的API)中的现实警告。
$ clang -fsyntax-only t.c
t.c:22:2: warning: type specifier missing, defaults to 'int'
ILPAD();
^
t.c:17:17: note: expanded from:
#define ILPAD() PAD((NROW - tt.tt_row) * 10) /* 1 ms per char */
^
t.c:14:2: note: expanded from:
register i; \
^
运行质量和对细节的关注
$ cat t.cc
template<class T>
class a {};
struct b {}
a<int> c;
$ gcc-4.9 t.cc
t.cc:4:8: error: invalid declarator before 'c'
a<int> c;
^
$ clang t.cc
t.cc:3:12: error: expected ';' after struct
struct b {}
^
;
下面的例子表明,即使在GCC无法应对的复杂情况下,clang也能很好地诊断和恢复缺失的类型名关键词。
$ cat t.cc
template<class T> void f(T::type) { }
struct A { };
void g()
{
A a;
f<A>(a);
}
$ gcc-4.9 t.cc
t.cc:1:33: error: variable or field 'f' declared void
template<class T> void f(T::type) { }
^
t.cc: In function 'void g()':
t.cc:6:5: error: 'f' was not declared in this scope
f<A>(a);
^
t.cc:6:8: error: expected primary-expression before '>' token
f<A>(a);
^
$ clang t.cc
t.cc:1:26: error: missing 'typename' prior to dependent type name 'T::type'
template<class T> void f(T::type) { }
^~~~~~~
typename
t.cc:6:5: error: no matching function for call to 'f'
f<A>(a);
^~~~
t.cc:1:24: note: candidate template ignored: substitution failure [with T = A]: no type named 'type' in 'A'
template<class T> void f(T::type) { }
^ ~~~~
【官方网站】
【最新版本】
10.0.0于2020年3月24日
【License】
Apache License 2.0 (LLVM除外)
【Clang-tidy】
clang-tidy是一个基于clang的C++"linter "工具。它的目的是提供一个可扩展的框架,用于诊断和修复典型的编程错误,如样式违规、界面错误或通过静态分析推导出的BUG。
【用法】
clang-tidy是一个基于LibTooling的工具。你也可以在命令行中在---之后指定编译选项。
clang-tidy test.cpp -- -Imy_project/include -DMY_DEFINES ...
clang-tidy有自己的检查,也可以运行Clang静态分析器的检查。每个检查都有一个名字,可以使用 -checks= 选项来选择要运行的检查,它指定了一个由正数和负数(前缀为-)globs组成的逗号分隔的列表。正的globs会添加检查的子集,负的globs会删除它们。例如:
clang-tidy test.cpp -checks=-*,clang-analyzer-*,-clang-analyzer-cplusplus*
以上命令将禁用所有默认检查 (-*) 并启用所有 clang-alyanzer-* 检查,但 clang-alyanzer-cplusplus* 检查除外。
-list-checks 选项列出了所有已启用的检查。如果不使用 -checks=,它将显示默认启用的检查。使用 -checks=* 来查看所有可用的检查,或者使用 -checks= 的任何其他值来查看该值所启用的检查。
目前有以下几组检查内容:
名称前缀 |
描述 |
abseil- |
与Abseil程序库相关的检查 |
android- |
与Android相关的检查 |
boost- |
与Boost程序库相关的检查. |
bugprone- |
检查容易出现错误的目标代码构造 |
cert- |
与CERT安全编码指南相关的检查。 |
clang-analyzer- |
Clang静态分析检查。 |
cppcoreguidelines- |
与C++核心指南相关的检查。 |
darwin- |
与达尔文编码公约相关的检查。 |
fuchsia- |
检验相关的Fuchsia编码惯例。 |
google- |
与谷歌编码公约相关的检查。 |
hicpp- |
高完整性C++编码标准的相关检查。 |
linuxkernel- |
与Linux内核编码公约相关的检查。 |
llvm- |
与LLVM编码公约有关的检查。 |
llvmlibc- |
与LLVM-libc编码标准相关的检查。 |
misc- |
杂类标准检查 |
modernize- |
对现代(目前 "现代 "是指 "C++11")语言结构的检查。 |
mpi- |
与MPI(信息传递接口)相关的检查。 |
objc- |
与Objective-C编码惯例相关的检查。 |
openmp- |
与OpenMP API相关的检查。 |
performance- |
针对性能相关问题的检查。 |
portability- |
针对与任何特定编码风格无关的可移植性相关问题进行检查。 |
readability- |
针对与任何特定编码风格无关的可读性相关问题进行检查。 |
zircon- |
与Zircon内核编码公约相关的检查。 |
Clang诊断程序的处理方式与检查诊断程序类似。Clang诊断程序由clang-tidy显示,可以使用-checks=选项过滤掉。但是,-checks=选项并不影响编译参数,所以它不能打开Clang警告,也就是说这些警告在编译配置中没有打开。-warnings-as-errors= 选项会将在 -checks= 选项下发出的警告升级为错误(但它本身不会启用任何检查)。
Clang 诊断程序的检查名称以 clang-diagnostic-开头。有相应的警告选项的诊断程序被命名为 clang-diagnostic-<warning-option>,例如,由 -Wliteral-conversion 控制的 Clang 警告将以检查名称 clang-diagnostic-literal-conversion 进行报告。
如果有相应的检查支持,-fix标志会指示clang-tidy修复发现的错误。
所有命令行选项可用如下命令显示:
clang-tidy --help
【抑制不希望的诊断】
clang-tidy诊断的目的是查出不符合编码标准的代码,或者在其他方面有问题的代码。但是,如果已知的代码是正确的,则有必要消除警告。有些clang-tidy检查提供了特定的检查方式来消除诊断,比如bugpron-use-after-move后的变量可以通过在变量被移出后重新初始化来消除警告,bugpron-string-integer-assignment可以通过显式转换将整数转换为char来抑制,可读性-implicit-bool-conversion也可以通过显式转换来抑制等等。
如果特定的压制机制对某一警告不适用,或者由于某种原因不希望使用它,clang-tidy有一个通用的机制,可以使用NOLINT或NOLINTNEXTLINE注释来压制诊断。
NOLINT注释指示clang-tidy忽略同行中的警告(它不适用于一个函数、一个代码块或任何其他语言结构,它仅仅适用于所在的代码行)。如果在同一行中引入注释会改变格式化,那么NOLINTNEXTLINE注释允许在下一行中压制clang-tidy警告。
这两个注释都可以在后面的括号里加上一个可选的校验名列表(见下面的正式语法)。
比如:
class Foo {
// 禁止该行的所有诊断程序。
Foo(int param); // NOLINT
// 考虑解释一下消除警告的动机。
Foo(char param); // NOLINT: 允许从 "char "进行隐式转换,因为<某种有效理由>。
// 只对指定的检查行进行消除
Foo(double param); // NOLINT(google-explicit-constructor, google-runtime-int)
// 只消除对下一行的指定诊断
// NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int)
Foo(bool param);
};
NOLINT/NOLINTNEXTLINE的正式语法如下:
lint-comment:
lint-command
lint-command lint-args
lint-args:
( check-name-list )
check-name-list:
check-name
check-name-list , check-name
lint-command:
NOLINT
NOLINTNEXTLINE
注意,在NOLINT/NOLINTNEXTLINE和开头的括号之间不允许使用空格,而在校验名列表中(在括号内)可以使用空格,并将被忽略。
【官方网站】
https://clang.llvm.org/extra/clang-tidy
【最新版本】
Extra Clang Tools 10
【License】
Apache License 2.0
【Coccinelle】
Coccinelle(法语为瓢虫的意思)是一个开源工具,用于匹配和转换用C语言编写的程序源代码。
Coccinelle最初是用来帮助Linux内核进化的,它提供了对库应用程序设计接口(API)的修改支持,例如重命名一个函数,添加一个值与上下文相关的函数参数,以及重组数据结构。
它还可以用来发现代码中存在缺陷的编程模式(即高概率错误的代码片段,如可能出现的NULL指针去掉指针等),而不需要对其进行改造。那么coccinelle的作用就接近于静态分析工具的作用。这类使用的例子有herodotos工具的应用,它可以跟踪coccinelle产生的警告。
对Coccinelle的支持是由IRILL提供的。开发资金由法国国家研究机构、丹麦技术和生产科学研究委员会和INRIA提供。
Coccinelle的源代码是根据GNU通用公共许可证(GPL)第2版的条款授权的。
语义补丁语言
要匹配或替换的源码使用基于补丁语法的 "语义补丁 "语法来指定,语义补丁语言(Semantic Patch Language,SmPL)模式近似于基于C语言的声明的统一的差别。
例子
@@
expression lock, flags;
expression urb;
@@
spin_lock_irqsave(lock, flags);
<...
- usb_submit_urb(urb)
+ usb_submit_urb(urb, GFP_ATOMIC)
...>
spin_unlock_irqrestore(lock, flags);
@@
expression urb;
@@
- usb_submit_urb(urb)
+ usb_submit_urb(urb, GFP_KERNEL)
【官方网站】
【最新版本】
1.0.7于2018年8月31日
【License】
GPLv2
【CPAchecker】
CPAchecker是一个用于C语言程序的正式软件验证和程序分析的框架和工具。它的一些思想和概念,例如懒惰抽象,是继承自软件模型检查器BLAST。CPAchecker是基于可配置程序分析的思想,它的概念是允许用一个规矩来表达模型检查和程序分析。在执行时,CPAchecker会进行可达性分析,即检查违反给定规范的某一状态是否有可能达到。
CPAchecker的一个应用是对Linux设备驱动程序的验证。
业内表现
CPAchecker在2012年在塔林举行的TACAS 2012首届软件验证大赛(2012年)中获得了两个组别(综合类和ControlFlowInteger)的第一名。
CPAchecker在2013年在罗马举行的第二届软件验证大赛(2013年)中获得了第一名(综合类)
架构
CPAchecker基于控制流自动机(CFA)进行操作;在CPA算法分析一个给定的C程序之前,它被转化为CFA。CFA是一个定向图,其边代表假设或分配,节点代表程序位置。
【官方网站】
http://cpachecker.sosy-lab.org/
【最新版本】
1.9于2019年11月
【License】
Apache License 2.0
【Cppcheck】
Cppcheck是C/C++编程语言的静态代码分析工具。这是一个通用的工具,可以检查非标准代码。创建者和首席开发人员是Daniel Marjamüki。
cppcheck是GNU通用公共许可证下的免费软件。
特点
Cppcheck支持编译器本身可能无法覆盖的各种静态检查。这些检查是静态分析检查,可以在源代码级别执行。该程序是针对静态分析检查是严格的,而不是启发式的性质。 支持的一些检查包括:
l 自动变量检查
l 数组越界的界限检查
l 类检查(如:未使用的函数、变量初始化和内存复制)
l Open Group中弃用或替代函数的使用
l 异常安全检查,如内存分配使用、析构函数检查等
l 内存泄漏,例如由于未进行解分配而丢失范围
l 资源泄漏,如忘记关闭文件句柄
l 标准模板库函数和习语的无效使用
l 使用unusedFunction选项消除死代码 杂项文体和性能错误
和许多分析程序一样,有许多编程习惯用法的不寻常情况,这些习惯用法在特定的目标情况下是可以接受的,或者在程序员的源代码修正范围之外。2009年3月进行的一项研究表明,Cppcheck在几个领域发现了误报,但没有具体说明检查的程序版本。cppcheck已被确定用于CERNs4DSOFTmeta分析程序包等系统,用于高能粒子探测器读出装置的代码验证、射电望远镜的系统监测软件以及大型项目的误差分析。例如:OpenOffice.org、Debian归档文件等。
开发状态
该项目正在积极开发之中,并积极在不同发行地进行维护。它在许多流行的项目中发现了有效的错误,比如Linux内核和MPlayer。
插件
存在以下IDE的插件:
CLion
Code::Blocks
CodeLite
Eclipse
Emacs
gedit
Hudson
Jenkins
Kate
KDevelop
Qt Creator
Sublime Text
Visual Studio
Yasca
【官方网站】
http://cppcheck.sourceforge.net/
【最新版本】
1.9于2019年12月
【License】
GPL
【Cpplint】
cpplint或cpplint.py是Google开发的一款开源的lint-like工具,旨在确保C++代码符合Google的编码风格规范。
因此,cpplint实现了Google认为的C++编码方面的最佳实践。脚本cpplint.py读取源代码文件并标记与样式指南的偏差。它还可以识别语法错误。它是基于规则的,并使用许多启发式方法来识别坏代码。
cpplint.py存在误报和漏报。误报可以通过添加// NOLINT(或// NOLINT(rule))来消除,只抑制被归罪的规则类别。
此外,可以使用--verbose和--filter选项来选择细粒度的规则。线长规则可配置option --linelength,文件扩展名可配置--extensions(默认'h', 'cpp', 'cc', 'cu', 'cuh')。一些选项可以存储在配置文件CPPLINT.cfg中。
Cpplint是用Python脚本实现的。
【官方网站】
https://github.com/google/styleguide/tree/gh-pages/cpplint
【最新版本】
1.4.4于2019年2月23日
【License】
BSD License 3
【Codan】
Codan是CDT中的轻量级静态分析框架,允许轻松插入“检查器”,对代码进行实时分析,以发现常见缺陷、违反策略等。
Codan框架使用对象:
工具供应商
l 创建包含终端用户检查器和模板的插件。
l 将命令行静态分析工具集成到CDT中,使用通用的前端工具
开发人员、测试人员、代码检查员
l 在开发过程中,为了在输入时检查是否有错误,并有一个快速修复错误的方法。
l 在代码检查过程中和代码执行前,发现BUG、安全违规、违反API、违反编码标准、违反编码标准等问题。
软件架构师,流程执行
l 根据模板创建自定义的新检查器(不涉及编程)。
l 创建问题简介
框架包含了C/C++静态分析工具之间共享的通用组件和API,如:
l 用户界面控制问题的启用和参数控制
l 不同的加载模式(类型,需求,构建者)。
l 一个eclipse视图,用于显示额外的问题信息(如额外的回溯线或更复杂的问题参数)。
l 一种通用标记类型,适用于有额外字段的问题。
l 用来记录问题的API
l 检查器的基础类
l JUnit测试框架
l 检查器的例子
l 代码的控制流程图
l 用于访问C/C++ AST和Scanner信息的API供检查器开发者使用(不属于此插件,但属于CDT核心的一部分)。
l 快速修复的例子(和一些辅助类)
l 检查器的常用范围过滤器
用户体验
【运行方式】
代码分析可以通过以下方式之一来调用:
l 从导航上下文菜单中选择 "运行代码分析 "命令(文件、目录、项目或组合)。
l 在项目首选项 "C/C++代码分析->构建 "中启用 "Run on Build",在这种情况下,它将以增量和完全构建的方式运行。
l 当你输入代码时。
l 保存文件时。
l 从命令行运行
eclipse -noSplash -application org.eclipse.cdt.codan.core.application -data <workspace> -consoleLog -verbose -all
Usage: [options] <project1> <project2> ...
Options:
-all - run on all projects in workspace
-verbose - print verbose build information
使用方法: [选项] <project1> <project2> ....
选项:
-all - 在工作区中的所有项目上运行
-verbose - 打印verbose构建信息。
注意:在windows系统上运行eclipsec。
如果你想将其作为自动构建的一部分运行,你还需要
a) 创建工作区并在命令行中引用
b) 将所有需要的项目导入到工作区中。
c) 确保所有项目都有索引
上面的所有操作都可以通过命令行使用另一个 cdt 应用程序 "org.eclipse.cdt.managedbuilder.core.headlessbuild",通过参数-import 或 -importAll 选项来完成
【视图】
如果发现了问题,它们将显示在 "问题 "视图中。每个问题都有一个特定的类型,可以通过视图控件进行分组或过滤。
问题详情视图可以显示关于问题的额外信息,它可以自定义(通过插件)显示任何外部链接或关于问题的额外信息,而这些额外信息在问题视图中是不可用的。默认情况下,它显示完整的文件和路径,完整的信息和问题描述。
【定制化】
用户可以在工作空间级别或项目级别上控制问题的严重性和启用开关。
很多问题都是可以自定义的,你可以设置额外的检查器选项和/或设置一个范围。
Splint是Secure Programming Lint的简称,是一个用于静态检查C程序是否存在安全漏洞和编码错误的编程工具。它的前身叫LCLint,是Unix lint工具的现代版本。
Splint具有对源代码的特殊注释进行解释的能力,这使得它的检查能力比单看源代码更强。Splint被gpsd作为零缺陷设计的一部分。
Splint是在GNU通用公共许可证条款下发布的自由软件。
Splint 的开发活动在 2010 年停止了。根据SourceForge的CVS,截止到2012年9月,版本库中最近的变化是在2010年11月。截至2018年6月,GitHub上的git仓库也是如此,显示自2010年11月以来没有任何变化。
【例子】
问题代码
#include <stdio.h>
int main()
{
char c;
while (c != 'x');
{
c = getchar();
if (c = 'x')
return 0;
switch (c) {
case '\n':
case '\r':
printf("Newline\n");
default:
printf("%c",c);
}
}
return 0;
}
Splint's 输出
Variable c used before definition
Suspected infinite loop. No value used in loop test (c) is modified by test or loop body.
Assignment of int to char: c = getchar()
Test expression for if is assignment expression: c = 'x'
Test expression for if not boolean, type char: c = 'x'
Fall through case (no preceding break)
定义前使用的变量c
怀疑是无限循环。循环测试中使用的值(c)没有被测试或循环体修改。
将int指定为char:c = getchar()
If内是赋值表达式: c = 'x'
If内不是布尔型数值,类型为char: c = 'x'
Case中没有break
修改后的代码
#include <stdio.h>
int main()
{
int c = 0; // 添加了一个初始赋值定义
while (c != 'x') {
c = getchar(); // 修正后的c类型为int
if (c == 'x') // 修正了赋值错误,使其成为比较运算符。
return 0;
switch (c) {
case '\n':
case '\r':
printf("Newline\n");
break; // 添加了break语句以防止掉线。
default:
printf("%c",c);
break; //在默认的分支中添加了break语句。
}
}
return 0;
}
【官方网站】
【最新版本】
3.1.2于2010年8月5日
【License】
GPL
【小结】
本文对于开源项目类别的C/C++编译器和静态分析工具进行了探究,可以看出每种工具都有其专注的领域,在业务开发过程中,我们应该从实际出发,针对已经存在的大量历史性代码,如果他们的编写风格不统一,要分阶段,尝试不同的工具来进行优化,对于遇到的问题附带解决的方法,应该整理出来形成文档,从而对以后的业务开发提供帮助和借鉴。
- 点赞
- 收藏
- 关注作者
评论(0)