Clang技术问询:如何获取常量数组声明中表示大小的宏名
解决Clang LibTooling中获取数组大小宏名的问题
我之前在开发源码转换工具时也碰到过一模一样的问题——预处理后宏会被直接展开成数值,AST里只能看到2049,完全拿不到原始的STR_MAX宏名。其实核心解法是借助Clang的SourceManager和Preprocessor API,从数组大小表达式的源位置回溯宏的展开链,找到最原始的宏定义名称。下面是具体的实现思路和代码示例:
核心思路拆解
当你拿到数组变量的VarDecl后,不要直接取预处理后的数值,而是要:
- 定位数组大小表达式的原始源位置(也就是
char inStr[STR_MAX];中STR_MAX的位置) - 通过
SourceManager判断该位置是否来自宏展开 - 回溯宏的调用链,找到最上层的用户定义宏名
具体代码实现
下面是基于RecursiveASTVisitor的完整示例,包含宏名回溯的核心逻辑:
#include "clang/AST/ASTContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/Lexer.h" class StrcpyRewriter : public RecursiveASTVisitor<StrcpyRewriter> { private: ASTContext& Ctx; Preprocessor& PP; SourceManager& SM; public: StrcpyRewriter(ASTContext& Ctx) : Ctx(Ctx), PP(Ctx.getPreprocessor()), SM(Ctx.getSourceManager()) {} bool VisitCallExpr(CallExpr* Call) { // 过滤出strcpy函数调用 if (FunctionDecl* FD = dyn_cast<FunctionDecl>(Call->getCalleeDecl())) { if (FD->getName() != "strcpy") return true; // 获取第一个参数(目标数组)并剥除隐式转换 Expr* DestArg = Call->getArg(0)->IgnoreImpCasts(); if (DeclRefExpr* DestRef = dyn_cast<DeclRefExpr>(DestArg)) { if (VarDecl* DestVar = dyn_cast<VarDecl>(DestRef->getDecl())) { QualType VarTy = DestVar->getType(); // 判断是否为常量数组类型 if (ConstArrayType* ArrayTy = dyn_cast<ConstArrayType>(VarTy.getTypePtr())) { Expr* SizeExpr = ArrayTy->getSizeExpr(); SourceLocation SizeLoc = SizeExpr->getBeginLoc(); // 回溯宏展开链,找到原始宏名 StringRef TargetMacroName; SourceLocation CurrentLoc = SizeLoc; while (SM.isMacroExpansion(CurrentLoc)) { CurrentLoc = SM.getImmediateMacroCallerLoc(CurrentLoc); if (MacroInfo* MI = PP.getMacroInfo(CurrentLoc)) { // 跳过内置宏,只保留用户定义的宏 if (!MI->isBuiltinMacro()) { TargetMacroName = MI->getName(); break; } } } if (!TargetMacroName.empty()) { // 这里成功拿到了STR_MAX,可以用来构建strcpy_s调用 llvm::errs() << "找到数组大小宏名: " << TargetMacroName << "\n"; // 后续可结合RefactoringTool生成替换代码 } else { // 处理数组大小为字面量的情况 llvm::APSInt SizeVal; if (SizeExpr->EvaluateAsInt(SizeVal, Ctx)) { llvm::errs() << "数组大小为字面量: " << SizeVal << "\n"; } } } } } } return true; } };
关键细节说明
- 剥除隐式转换:用
IgnoreImpCasts()去掉数组参数被隐式转换为void*的包装,才能拿到原始的DeclRefExpr - 宏链回溯:通过
getImmediateMacroCallerLoc()逐层向上找宏的调用位置,直到找到用户定义的顶层宏(跳过__LINE__这类内置宏) - 保留源信息:确保LibTooling运行时没有丢弃宏的展开记录——默认配置下Clang会保留这些信息,无需额外开启特殊参数
后续扩展
拿到宏名后,你可以用Rewriter或RefactoringTool生成strcpy_s(inStr, STR_MAX, argv[1])这样的调用,替换原来的strcpy语句。如果需要处理更复杂的宏嵌套(比如#define A 2049 #define STR_MAX A),可以修改循环逻辑,一直回溯到最顶层的宏定义。
内容的提问来源于stack exchange,提问作者d3v-sci
相关产品推荐
相关产品推荐

