首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 獲取函數列表的工程化實踐:從正則匹配到 Clang LibTooling

     編輯:關於C++

背景與痛點

在代碼覆蓋率工具開發中,需解析 C++ 源文件提取所有函數定義(用於插桩)。初期採用「括號匹配 + 關鍵字識別」的手寫解析器,隨項目深入暴露嚴重問題:

  • 宏幹擾:函數聲明中含宏(如 `MY_API void foo();`),無法識別函數名。
  • 括號不平衡:函數體內宏展開導致 `{}` `()` 失配,陷入死循環。
  • 模板/命名空間/屬性:C++ 語法極其複雜,手寫規則永遠追不上邊缘案例。

方案演進

1. 手寫規則引擎(初版)

掃描字符,維護棧匹配 `{}` `()`,遇到 `)` 後跟 `{` 判定為函數定義。
**失敗案例**:宏導致括號數錯誤、函數指釖、lambda、類內定義等。

2. 借鑒 Notepad++ FunctionList 插件

發現其使用正則表達式配置文件定義語言解析規則。嘗試移植其 C++ 正則規則。
**優點**:快速獲得基礎支援。
**缺點**:正則本質上無法處理遞歸結構(模板嵌套、括號匹配),異常代碼仍會漏報/誤報;且規則難以擴展。

3. 方案:Clang LibTooling (LibTooling / LibAST)

**正解**:使用 Clang 前端庫直接解析 AST,提取 `FunctionDecl` 節點。
**優勢**:

  • 完整支援 C++ 標準(含模板、宏展開後語義、屬性、概念等)。
  • 獲取精確位置(行/列)、簽名、返回類型、參數、所在類/命名空間。
  • 可輕鬆集成到構建系統(編譯數據庫 `compile_commands.json`)。

核心代碼示例


// 基於 RecursiveASTVisitor
class FunctionFinder : public RecursiveASTVisitor {
public:
    explicit FunctionFinder(ASTContext* ctx) : ctx_(ctx) {}
    bool VisitFunctionDecl(FunctionDecl* f) {
        if (f->isThisDeclarationADefinition()) {
            SourceLocation loc = f->getLocation();
            auto spelling = f->getQualifiedNameAsString();
            auto file = ctx_->getSourceManager().getFilename(loc);
            unsigned line = ctx_->getSourceManager().getSpellingLineNumber(loc);
            // 輸出或存儲:文件、行號、函數全名
            printf("%s:%d: %s\n", file.str().c_str(), line, spelling.c_str());
        }
        return true;
    }
private:
    ASTContext* ctx_;
};

// FrontendAction 創建 ASTConsumer 運行訪問者
// 需配合 CommonOptionsParser 讀取 compile_commands.json

編譯與運行

  • 生成 `compile_commands.json`(CMake 設置 `CMAKE_EXPORT_COMPILE_COMMANDS=ON`)。
  • 鏈接 `clangTooling`, `clangFrontend`, `clangAST`, `clangBasic`, `llvmSupport` 等庫。
  • 運行工具:`./my_tool -p build_dir source.cpp`。

替代輕量方案:C99 `__func__` / `__FUNCTION__` 宏

若僅需**運行時**在函數內獲取自身名稱(如日誌、插桩),C99 標準宏 `__func__`(C++11 `__FUNCTION__`)極其簡單:


#define LOG() printf("[%s:%d] %s\n", __FILE__, __LINE__, __func__)
void foo() { LOG(); ... }

**局限**:無法靜態獲取全項目函數列表,只能在函數體內使用。

總結建議

需求推薦方案複雜度
靜態分析全項目函數列表(含簽名、位置)Clang LibTooling (LibAST)中(需編譯環境)
輕量級語法高亮/大綱視圖(IDE 插件)Tree-sitter / Language Server Protocol低
運行時函數名獲取(日誌/插桩)`__func__` / `__FUNCTION__` 宏極低
簡單腳本提取(不追求完美)Tree-sitter 查詢 / 正則 + 簡單狀態機低

核心教訓:C++ 語法過於複雜,**不要嘗試手寫解析器**。利用成熟的編譯器前端庫是工業級解決方案的唯一正確道路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved