【源码解析】C/C++开源代码解析引擎

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编译环境

  1. 下载并安装LLVM,确保Clang工具链已安装;

  2. 将LLVM的bin目录添加到系统的PATH环境变量中,以便Visual Studio可以找到’clang-cl.exe’;

  3. 或者使用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 更改项目的工具集
  1. 打开项目的“属性”窗口;

  2. 在“配置属性”下,选择“常规”;

  3. 在“平台工具集”(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解析器生成工具。

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值