1. 背景说明
针对Simulink或其他MBD环境的模型生成代码,及其他的外部C/C++代码工程,做相应的后端代码优化处理工作,例如如下场景,
-
统计代码内的全局变量声明及其内存占用情况;
-
提取代码内的逻辑判断条件结合Z3 Prover定理证明器进行形式化验证;
-
...
因此需要对C/C++代码进行语法分析、代码分析、抽象语法树(AST)生成等操作,特此调研开源的C/C++代码解析引擎,并形成此文档。
2. 常见的开源C/C++代码解析引擎
2.1 Clang/LLVM
-
简介:Clang是一个C、C++和Objective-C的编译器前端,基于LLVM项目。它不仅提供了编译器功能,还提供了丰富的API用于代码分析和工具开发。
-
功能:Clang提供了抽象语法树(AST)接口、语义分析、代码格式化、重构等功能。
-
优势:强大的诊断和错误信息、模块化设计、广泛的社区支持。
-
授权:Apache License v2.0
2.1.1 LLVM项目
LLVM(Low-Level Virtual-Machine)是一个模块化和可重用的编译器和工具链技术项目。最初由克里斯·拉特纳(Chris Lattner)于2000年在伊利诺伊大学厄巴纳-香槟分校的博士项目中开发,现在由LLVM基金会和全球社区维护。LLVM项目包含一组编译器基础设施,分别用于开发前端编译器、中间语言优化和后端代码优化工具。
2.1.1.1 LLVM的核心组件和架构
2.1.1.1.1 LLVM Core
LLVM核心库,提供了中间表示(IR) 的定义、基本块和函数的表示,以及一系列用于操作和分析这些结构的工具。LLVM IR是一种低级、高度优化的语言独立中间表示,可以跨平台编译。
2.1.1.1.2 Clang
Clang是LLVM项目的一个C、C++和Objective-C编译器前端。它提供了代码解析、AST生成和代码分析等功能,旨在提供快速的编译速度和优秀的诊断信息。
2.1.1.1.3 LLVM Optimizer
LLVM优化器利用LLVM IR进行各种优化处理,如常量传播、死代码消除、循环优化等。这些优化可以在编译时(静态优化)或运行时(JIT编译时)进行。
2.1.1.1.4 LLVM Code Generator
LLVM后端部分,它负责将LLVM IR转换为特定架构的机器代码。LLVM支持多种硬件架构,包括x86、ARM、PowerPC、RISC-V等。
2.1.1.1.5 LLVM Runtime Libraries
LLVM项目还提供了一些运行时库,如编译时支持库(libc++、libc++abi等),用于支持生成的代码。
2.1.1.1.6 LLVM工具和实用程序
包括调试器(lldb)、二进制工具(llvm-objdump、llvm-ar等)、静态分析工具(scan-build)等。
2.1.1.2 LLVM的特点
2.1.1.2.1 模块化设计
LLVM的各个部分是高度模块化的,允许开发者根据需求组合使用。可以单独使用Clang作为前端编译器,也可以利用LLVM优化器和代码生成器。
2.1.1.2.2 语言独立性
LLVM IR是一种通用的中间表示,可以用来表示各种高级编程语言的编译结果,这使得LLVM可以支持多种语言前端。
2.1.1.2.3 广泛的硬件支持
LLVM具有广泛的硬件架构支持,这使得他成为各平台开发的理想选择。
2.1.1.2.4 实时编译(JIT)
LLVM支持及时编译(JIT),这使得它适用于动态语言的执行引擎开发和运行时优化。
2.1.1.2.5 开源社区
LLVM是开源项目,有一个活跃的社区贡献者和用户。它被广泛用于学术研究和工业界的编译器开发。
2.1.1.3 LLVM的应用领域
2.1.1.3.1 编译器开发
LLVM被广泛用于开发各种编程语言的编译器,如Rust编译器(rustc)、Julia编译器、Swift编译器等。
2.1.1.3.2 工具链和静态分析
LLVM的工具链被用于开发各种代码分析工具、格式化工具和静态分析器。
2.1.1.3.3 实时编译和运行时优化
由于支持JIT,LLVM被用于实时编译和运行时优化,应用在游戏引擎、数据库、浏览器引擎等领域。
2.1.1.3.4 研究和教育
由于模块化和开放性,LLVM常被用于编译器研究和教育领域。
2.1.1.3.5 工业应用
许多公司在器开发工具链中使用LLVM,包括Apple、Google、Microsoft、Sony等。
2.2 GCC(GNU Compiler Collection)
-
简介:GCC是一个编译器系统,支持多种编译语言,其中包括C和C++。它的GCC内部表示(GIMPLE、RTL等)也可以用于代码分析和优化。
-
功能:除了编译功能外,GCC还提供了静态分析工具(如GCC Static Analyzer)、插件机制等。
-
优势:支持广泛的架构和语言,经过长时间验证的工具链。
-
授权:GPL 2.0 License
2.3 LibClang
-
简介:LibClang是Clang的C接口,提供了一个相对简单的接口来解析C/C++代码,并访问Clang的AST。
-
功能:提供了基础的代码解析、符号查找、诊断信息等功能,适合构建简单的代码分析工具。
-
优势:使用简单,直接获取Clang的强大功能。
-
授权:Apache License v2.0
2.4 CppCheck
-
简介:CppCheck是一个静态代码分析工具,专门用于查找C/C++代码中的错误。
-
功能:检查常见的编程错误、内存泄漏、未定义行为等;
-
优势:专注于安全性和代码质量,轻量级,易于集成。
-
授权:GPL-3.0 license
2.5 Ctags
-
简介:Ctags生成标签文件,该文件记录了源代码中各种标识符的位置。支持C、C++及其他多种语言。
-
功能:支持符号导航、代码搜索等功能,常与编译器集成。
-
优势:支持广泛的语言和编译器,简单而高效。
-
授权:GPL-2.0 license
2.6 Clangd
-
简介:Clangd是基于Clang的语言服务器协议(LSP)实现,提供了对C/C++代码的智能感知支持。
-
功能:自动完成、跳转定义、文档查找、诊断信息、代码格式化等。
-
优势:基于Clang,提供了丰富的功能和良好的编辑器集成支持。
-
授权:Apache-2.0 license
2.7 Bear
-
简介:Bear是一个生成Clang编译数据库的工具。它拦截编译命令,生成用于Clang工具链的’json’文件。
-
功能:主要用于生成编译数据库,以便在Clang工具链中使用。
-
优势:提供方便的方式为Clang工具链生成编译信息。
-
授权:GPL-3.0 license
3. ANTLR4与LibClang代码解析的优劣势
此处的代码解析,主要针对于Sysplorer/Sysblock模型生成的C语言代码,以及其他C/C++工程代码。且常见的开源代码解析引擎,相当部分是Clang项目或相关工具链,特此选择ANTLR4与LibClang进行对比。
ANTLR4(Another Tool For Language Recognition)是一个功能强大的解析器生成工具,用于从上下文无关语法(Context-Free Grammars,CFG)生成解析器。ANTLR4特别适合构建语言翻译工具、编译器、解释器等,它支持多种目标编程语言,包括Java、C#、JavaScript、Python等。
ANTLR4和LibClang是两种非常不同的工具,它们都可以用于C/C++代码解析,但在功能、试用场景和优劣势方面有着显著差异。
3.1 ANTLR4
3.1.1 优势
3.1.1.1 通用性强
ANTLR4是一个通用的解析器生成工具,不仅限于C/C++,还可以解析多种自定义语言或数据格式。可以通过编写语法文件(.g4)来定义任意语言的语法和词法规则。
3.1.1.2 灵活性
用户可以完全控制解析的方式和生成的解析树结构。可以为不同语言的特定需求定制解析器,添加特定的语义操作。
3.1.1.3 支持多种目标语言
ANTLR4支持生成多种目标语言的解析器,包括Java、C#、Python等,便于集成到不同编程语言的项目中。
3.1.1.4 Listener和Visitor模式
ANTLR4提供了方便的Listener和Visitor模式,用于遍历和操作解析树。这使得分析和转换代码变得更加直观和模块化。
3.1.2 劣势
3.1.2.1 手动编写语法文件
需要手动编写C/C++语言的语法文件,这对于复杂的语言(如C++)来说可能非常复杂和困难。要实现完整的C++语法支持是一项巨大的工程。
3.1.2.2 缺乏内置的语义分析
ANTLR4主要关注词法和语法分析,缺乏对C/C++语言特定的语义分析支持,例如符号解析、类型检查等。这需要额外的工作来实现。
3.1.2.3 没有内置的代码补全和错误修正功能
这些功能需要手动实现,与LibClang相比,需要更多的开发工作。
3.2 LibClang
3.2.1 优势
3.2.1.1 原生支持C/C++
LibClang是Clang的C接口,是一个专门为C/C++设计的工具。它完全理解C/C++的复杂语法和语义,支持包括模版、宏等复杂特性。
3.2.1.2 完整的AST生成
提供完整的抽象语法树(AST),精确地表示C/C++代码的结构。可以访问详细的类型信息、作用域、符号等。
3.2.1.3 内置语义分析
提供内置的语义分析功能,如符号解析、类型检查等。这对于开发人员需要深入理解代码语义的工具非常有用。
3.2.1.4 强大的工具集成
LibClang是Clang工具链的一部分,可以与Clang的其他工具(如代码格式化、静态分析、编译器等)紧密集成。
3.2.1.5 支持IDE集成
由于提供了精确的语法和语义信息,LibClang非常适用于实现IDE功能,如代码补全、跳转到定义、代码重构等。
3.2.2 劣势
3.2.2.1 语言限制
LibClang主要支持C、C++和Objective-C。如果需要解析其他语言,LibClang不是合适的工具。
3.2.2.2 复杂性和依赖性
LibClang依赖于Clang的完整工具链,使用和部署可能比其他工具更复杂。
3.2.2.2.1 配合Clang编译环境使用
LibClang是Clang的C接口库,它依赖于Clang的核心库和工具链来解析和处理C/C++代码。
使用LibClang接口通常需要配合Clang编译环境,这包括Clang编译器、相关库和工具,以及正确的编译环境设置。
以下说明了需要Clang编译环境支持的原因:
依赖Clang库
LibClang接口是Clang项目的一部分,它直接依赖于Clang的其他核心库(如’libclangAST’、’libclangParse’等)。这些库提供了解析、语法分析、语义分析等功能。
编译器工具链
LibClang常用于需要精确编译信息的场景,如IDE集成、代码分析等。它通常需要访问编译数据库(‘compile_commands.json’),该文件记录了如何编译项目的每个文件。生成这个文件通常是通过Clang的工具(如’bear’或’cmake’等)实现的。
Clang的特性和优化
Clang提供了一些特定的编译选项和优化(如诊断、代码完成等),这些特定在使用LibClang时也可以受益。
3.2.2.3 非易用的API
虽然LibClang提供了C接口,但操作AST和其他数据结构可能相对复杂。理解Clang的内部数据结构和设计需要一定的学习曲线。
3.3 总结
ANTLR更适合用于需要定制化的解析器开发,尤其是需要支持多种语言或自定义语言的场景。它的灵活性和通用性是其最大的优势,但在处理复杂语言(如C++)是需要额外的工作。
LibClang则是为C/C++量身定制的工具,能够精确地处理C/C++的所有语法特性和语义特性,适用于需要高精度解析和语义分析的工具,如IDE和静态分析器。它的深度和广度使其在C/C++代码解析领域非常强大,但也限制了其适用范围。
3.3.1 适用的开发流程
如果目标是快速、准确地解析C/C++语言代码并获取丰富的语义信息,LibClang是更合适的选择。对于需要高度定制化或跨语言支持的解析需求,且愿意投入较多开发工作量的项目,ANTLR4则提供了更大的灵活性。
4. Clang编译环境的配置与验证
下面介绍在Visual Studio开发环境中配置Clang编译器的基本说明。
4.1 在Visual Studio中使用Clang编译器
Visual Studio支持使用不同的编译器,包括Clang。在Windows上,Clang通常作为LLVM工具链的一部分安装,可以使用VS的“工具集”(toolset)来切换到Clang编译环境。
4.1.1 安装Clang编译环境
-
下载并安装LLVM,确保Clang工具链已安装;
-
将LLVM的bin目录添加到系统的PATH环境变量中,以便Visual Studio可以找到’clang-cl.exe’;
-
或者使用Visual Studio Installer配置Clang编译环境,详情参考,Visual Studio 项目中的 Clang/LLVM 支持。
图 4-1 Visual Studio安装程序的Clang环境
4.1.2 配置Visual Studio使用Clang编译环境
4.1.2.1 创建或打开项目
创建一个新的Visual Studio项目或打开现有项目。
4.1.2.2 更改项目的工具集
-
打开项目的“属性”窗口;
-
在“配置属性”下,选择“常规”;
-
在“平台工具集”(Platform Toolset)下拉菜单中选择LLVM(clang-cl)或者其他表示Clang的选项。
图 4-2 切换至Clang编译器
4.1.2.3 配置编译和链接器设置
在“C/C++”和“链接器”设置下,可以配置与Clang相关的选项,这些设置与使用MSVC编译器时类似。
图 4-3 Clang编译环境的"C/C++"设置
图 4-4 Clang编译环境的"链接器"设置
4.1.2.4 编译和运行
配置完成后,可以像平常一样编译和运行项目。Clang编译器会负责代码的编译和链接。
4.1.2.5 注意事项
-
兼容性:Clang可能对一些特定的MSVC扩展不完全兼容,尤其是涉及Windows特定API和特性时。需要测试和调整代码以确保兼容性。
-
诊断信息:Clang提供了不同的诊断和告警信息,可以在项目设置中进一步定制和调整。
-
经过验证,MSVC编译环境下可正常编译和运行LibClang程序,如下:
-
Microsoft Visual Studio 2019,版本11.32;
-
LLVM工具集,版本1.8。
-
4.2 Visual Studio工程项目验证示例
4.2.1 代码解析的C代码头文件
以下为执行代码解析的C代码头文件,header_de.h。
#pragma once
typedef char MwbChar;
typedef int MwbInt;
typedef unsigned int MwbUInt;
typedef float MwbFloat;
typedef double MwbDouble;
typedef int MwbBool;
typedef size_t MwbSize;
typedef signed char MwbInt8;
typedef unsigned char MwbUInt8;
typedef short MwbInt16;
typedef unsigned short MwbUInt16;
typedef int MwbInt32;
typedef unsigned int MwbUInt32;
double gFloat = 0.0f;
struct model81SdModel81 {
MwbDouble m_sample;
MwbInt m_errorCode;
MwbSolveStage m_solveStage;
MwbSolveStepStage m_solveStepStage;
MwbDouble m_startTime;
MwbInt m_timeTickCount;
MwbDouble m_curTime;
};
struct model81IdModel81U {
struct Bus1 inport;
Enum1 inport1;
MwbInt16 inport2;
};
如上代码段所示,待解析的C代码头文件包含如下信息,类型定义typedef语句、变量定义语句,以及结构体声明语句。
4.2.2 使用LibClang解析C代码示例
#include <iostream>
#include <clang-c/Index.h> // This is libclang.
#define FILE_PATH "C:\\Users\\TR\\source\\repos\\Clang\\header_de.h"
// 输出变量类型
void PrintVarDecl(CXCursor cursor, CXCursorKind cursor_type)
{
// 变量名
CXString name = clang_getCursorSpelling(cursor);
// 变量类型
CXType type = clang_getCursorType(cursor);
// 变量类型名
CXString type_name = clang_getTypeSpelling(type);
// 游标类型名
CXString cursor_type_name = clang_getCursorKindSpelling(cursor_type);
// 游标位置
CXSourceLocation location = clang_getCursorLocation(cursor);
CXFile file;
unsigned int line, column;
clang_getFileLocation(location, &file, &line, &column, NULL);
// 文件名称
CXString file_name = clang_getFileName(file);
printf("Cursor:%s, Type:%s[%s], Location:%s %d:%d\n",
clang_getCString(name),
clang_getCString(type_name),
clang_getCString(cursor_type_name),
clang_getCString(file_name), line, column);
// 资源释放
clang_disposeString(name);
clang_disposeString(type_name);
clang_disposeString(cursor_type_name);
clang_disposeString(file_name);
}
// 遍历AST节点的回调函数
// Visit Children,任何cursor都有一种类型enum CXCursorKind,表示cursor的本质
CXChildVisitResult visitor(CXCursor cursor, CXCursor parent, CXClientData client_data)
{
CXCursorKind eType = clang_getCursorKind(cursor);
PrintVarDecl(cursor, eType);
return CXChildVisit_Recurse;
}
int main()
{
CXIndex index = clang_createIndex(0, 0);
// 解析成功后,获取经过解析的抽象语法树(AST),可以遍历和检查该语法树
CXTranslationUnit unit = clang_parseTranslationUnit(index,
FILE_PATH,
nullptr, 0, nullptr, 0,
CXTranslationUnit_None);
if(nullptr == unit)
{
std::cerr << "Unable to parse translation unit. Qutting." << std::endl;
return -1;
}
// 指向抽象语法树(AST)的指针成为Cursors
CXCursor cursor = clang_getTranslationUnitCursor(unit);
// 调用回调函数
clang_visitChildren(cursor, visitor, nullptr);
// 资源释放
clang_disposeTranslationUnit(unit);
clang_disposeIndex(index);
return 0;
}
4.2.2.1 LibClang接口解析的通用流程
使用LibClang解析源码并遍历抽象语法树AST的通用流程包括以下步骤:
4.2.2.1.1 初始化和配置
-
创建索引:创建一个索引对象,通常一个编译器示例对应一个索引对象;
-
设置库路径(可选):在使用Python绑定时,可能需要指定libclang库的路径。
-
在Windows环境使用相关函数时需要链接到LibClang库,需要链接以下库文件(包含在LLVM工具集内):
-
lib
-
静态库文件,链接该库可以使用LibClang提供的所有API。
-
-
dll
-
动态链接库,需要在运行时确保该文件在可执行文件路径内。
-
-
-
4.2.2.1.2 解析源代码
-
生成翻译单元:使用clang_parseTranslationUnit()函数解析源代码文件,生成翻译单元(Translation Unit)。这代表一个独立的解析结果,通常对应一个源代码及其包含的头文件。
4.2.2.1.3 获取根游标
-
获取根游标:翻译单元的根游标(Cursor)代表了整个翻译单元的根节点,可以通过clang_getTranslationUnitCursor()获取。
4.2.2.1.4 遍历AST
-
设置回调函数:定义一个回调函数来处理每个节点。这通常是通过检查节点的类型来过滤和处理感兴趣的节点,例如变量声明、函数定义等。
-
遍历节点:使用clang_visitChildren()函数从根游标开始递归遍历整个AST,该函数会调用回调函数来处理每个子节点。
4.2.2.1.5 处理节点
-
获取节点信息:在回调函数中,可以使用相关API获取节点的详细信息,包括类型、名称、位置等。
4.2.2.1.6 释放资源
-
释放翻译单元和索引:在完成解析和遍历后,释放分配的资源,以防止内存泄漏。
4.2.2.2 执行结果
上述示例代码的执行结果如下:
图 4-5 示例代码执行结果
如图 4-5所示,可获取类型定义TypedefDecl、变量定义VarDecl、结构体定义StructDecl,以及结构体变量定义FieldDecl等类型,及其所在的文件及行列。
5. 调研结论
如3.3.3所示,LLVM工具集是模块化和可重用的编译器和工具链技术项目,LibClang作为Clang的C接口,原生支持C/C++语法与语义,能够精确处理C/C++所有语法特性与语义特性,且Visual Studio 2019环境可原生支持编译和运行Clang环境,适用于需要高精度解析和语义分析的工具。
主要难点在于操作AST和其他数据结构可能相对复杂,需要一定的学习和理解过程。
如果需要高度定制化的词法与语法解析流程,可考虑ANTLR4解析器生成工具。