一 场景,我们在阅读源码的时候,
c默认调用看不到函数类型,函数返回值,所以需要查看函数原型。
二 功能,脚本是批量寻找所有引入的文件。 将函数调用和函数原型放在一起。
三,使用,termux 或linux,
cd... ,py ... 即可,它会提示你输入文件.c ,提示输入项目根目录。 会自动在根目录生成分析报告。
这里为什么不用人工智能?因为人工智能输出不固定,
四,代码
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ C函数定义与原型对比分析工具 用于分析C源文件中的函数定义,并在头文件中查找对应的原型声明 """ import os import re import sys from pathlib import Path import clang.cindex as CX # ---------- 配置 ---------- try: libclang_paths = [ 'libclang.so', '/usr/lib/x86_64-linux-gnu/libclang.so', '/usr/lib/llvm-15/lib/libclang.so', '/usr/lib64/libclang.so', '/usr/lib/libclang.so', '/usr/local/opt/llvm/lib/libclang.dylib' # macOS ] for path in libclang_paths: if os.path.exists(path): CX.Config.set_library_file(path) print("已找到并使用 libclang: " + path) break else: print("警告:未能自动找到 libclang。请确保已安装 libclang 并设置正确路径。") except Exception as e: print("配置 libclang 时出错: " + str(e)) # -------------------------- # 1. 生成Markdown代码块分隔符 def get_fence(): """返回三个反引号,用于生成Markdown代码块""" return chr(96) + chr(96) + chr(96) # 2. 用户输入辅助函数 def ask(prompt): """获取用户输入并去除首尾空格""" return input(prompt).strip() # 3. 收集项目中的所有头文件 def collect_headers(root: Path): """递归收集项目根目录下的所有.h头文件""" return list(root.rglob('*.h')) # 4. 解析C文件中的函数定义 def parse_c_file_definitions(c_path: Path): """ 使用libclang解析C文件,提取所有函数定义 参数: c_path: C源文件路径 返回: 字典,键为函数名,值为包含签名和位置的字典 """ func_defs = {} try: index = CX.Index.create() # 添加编译参数,包含系统头文件路径 args = ['-x', 'c', '-I/usr/include', '-I/usr/local/include'] tu = index.parse(str(c_path), args=args) if not tu: print("错误:无法解析文件 " + str(c_path)) return {} # 遍历抽象语法树 for cursor in tu.cursor.walk_preorder(): if cursor.kind == CX.CursorKind.FUNCTION_DECL and cursor.is_definition(): name = cursor.spelling # 过滤无效函数 if not name or not cursor.location.file: continue # 确保函数定义在当前文件中 if not str(c_path).endswith(cursor.location.file.name): continue # 获取位置信息 loc = str(Path(cursor.location.file.name).name) + ":" + str(cursor.location.line) # 使用libclang获取完整的函数签名 try: # 获取函数类型 func_type = cursor.type return_type = func_type.get_result().spelling # 获取参数列表 args_list = [] for arg in cursor.get_arguments(): arg_type = arg.type.spelling args_list.append(f"{arg_type} {arg.spelling}" if arg.spelling else arg_type) # 构建完整的函数签名 args_str = ", ".join(args_list) if args_list else "void" signature = f"{return_type} {name}({args_str})" func_defs[name] = {'signature': signature, 'loc': loc} except Exception as e: print("警告:获取函数签名失败: " + str(e)) except Exception as e: print("解析C文件时出错: " + str(e)) return func_defs # 5. 在头文件中查找函数原型 def find_prototype_in_headers(func_name, header_paths, c_path): """ 在头文件中搜索指定函数的原型声明 参数: func_name: 函数名 header_paths: 头文件路径列表 c_path: C源文件路径(用于排除自身) 返回: (原型字符串, 位置字符串) 或 (None, None) """ # 改进的正则表达式,更准确地匹配函数原型 # 匹配:返回类型 + 函数名 + 参数列表 + 分号 pattern = re.compile( r'^\s*(?:extern\s+|static\s+|inline\s+)*' # 可选的存储类说明符 r'(?:[\w\s\*]+\s+)' # 返回类型(包含指针修饰符) r'\b' + re.escape(func_name) + r'\s*' # 函数名 r'\([^;{]*\)\s*;' # 参数列表和分号 ) # 特殊处理:main函数通常不需要原型 if func_name == 'main': return None, None for h_path in header_paths: # 跳过C文件本身 if h_path == c_path: continue try: with open(h_path, 'r', encoding='utf-8', errors='ignore') as f: for line_num, line in enumerate(f, 1): # 移除行尾注释 line_clean = line.split('//')[0].strip() if pattern.search(line_clean): prototype_line = line_clean location = str(Path(h_path).relative_to(h_path.parents[2])) + ":" + str(line_num) return prototype_line, location except Exception as e: print(f"警告:读取头文件 {h_path} 失败: {str(e)}") return None, None # 6. 生成对比报告 def build_report(c_path, func_defs, header_paths): """ 生成函数定义与原型对比的Markdown格式报告 参数: c_path: C源文件路径 func_defs: 函数定义字典 header_paths: 头文件路径列表 返回: Markdown格式的报告内容 """ lines = [] # 报告标题 lines.append("# 函数定义与原型对比报告\n") lines.append(f"> 源文件:`{c_path}`\n") lines.append("---\n") # 检查是否找到函数定义 if not func_defs: lines.append("在源文件中未找到任何函数定义。") return "\n".join(lines) # 为每个函数生成对比部分 for i, (name, info) in enumerate(sorted(func_defs.items()), 1): lines.append(f"### {i}. 函数定义") lines.append(get_fence() + "c") lines.append(info['signature']) lines.append(get_fence()) lines.append(f"*定义位置:{info['loc']}*") lines.append("\n**🔍 对应原型**") # 在头文件中查找原型 prototype_line, location = find_prototype_in_headers(name, header_paths, c_path) if prototype_line: lines.append(get_fence() + "c") lines.append(prototype_line) lines.append(get_fence()) lines.append(f"*原型位置:{location}*") else: # 特殊说明 if name == 'main': lines.append("*`main` 函数是程序入口点,通常不需要在头文件中声明原型。*") else: lines.append("*未在项目头文件中找到匹配的原型。*") lines.append("\n---\n") return "\n".join(lines) # 7. 主程序入口 def main(): """主程序,协调整个分析流程""" # 获取C文件路径 c_file = Path(ask("请输入 .c 文件路径:")).expanduser().resolve() if not c_file.exists() or c_file.suffix != '.c': sys.exit("错误:文件不存在或不是 .c 后缀") # 获取项目根目录 proj_root = Path(ask("请输入项目根目录:")).expanduser().resolve() if not proj_root.is_dir(): sys.exit("错误:项目根目录不存在") # 收集头文件 header_paths = collect_headers(proj_root) if not header_paths: print("警告:在项目根目录下未找到任何 .h 文件。") # 解析C文件 print("正在解析 C 文件...") func_defs = parse_c_file_definitions(c_file) if not func_defs: sys.exit("在指定的 C 文件中未找到任何函数定义,分析结束。") # 生成报告 print("正在生成报告...") report_content = build_report(c_file, func_defs, header_paths) # 保存报告 report_dir = proj_root / "analysis_report" report_dir.mkdir(exist_ok=True) report_file = report_dir / (c_file.stem + "_vs_prototype.md") try: report_file.write_text(report_content, encoding='utf-8') print(f"\n✅ 分析完成!报告已生成:\n{report_file}") except Exception as e: print(f"\n❌ 写入报告文件失败: {str(e)}") if __name__ == '__main__': main()