Python代碼實現(xiàn)C++代碼依賴提取工具
編寫一個Python程序代碼,輸入C/C++源文件名和函數(shù)名字符串,解析一個目錄下所有C/C++的源代碼,找到相關(guān)的宏名、變量名和函數(shù)名對應的具體實現(xiàn)代碼,以及和它們相關(guān)的具體實現(xiàn)代碼,按依賴性順序?qū)懭胍粋€standalone.cpp的文件里。
下面是一個使用 libclang 提取 C/C++ 函數(shù)及其依賴并生成 standalone.cpp 的 Python 程序框架。由于完整實現(xiàn)非常復雜,這里給出了核心思路和關(guān)鍵代碼,實際使用時需要根據(jù)項目情況調(diào)整。
import os
import sys
import argparse
from collections import defaultdict, deque
from clang.cindex import Index, CursorKind, TokenKind, Config
# 如果 libclang 不在默認路徑,需要設置 Config.set_library_path()
# Config.set_library_path('/usr/lib/llvm-10/lib')
class DependencyExtractor:
def __init__(self, root_dir, target_func):
self.root_dir = os.path.abspath(root_dir)
self.target_func = target_func
self.index = Index.create()
self.symbols = {} # 符號名 -> 符號信息(類型、定義位置、代碼片段等)
self.dependencies = defaultdict(set) # 符號名 -> 依賴的符號名集合
self.referenced_by = defaultdict(set) # 反向依賴,用于拓撲排序
self.file_content_cache = {} # 文件內(nèi)容緩存,用于提取代碼片段
def parse_files(self):
"""遍歷目錄下所有 .c/.cpp/.h/.hpp 文件,解析并收集符號定義和引用關(guān)系"""
for root, _, files in os.walk(self.root_dir):
for file in files:
if file.endswith(('.c', '.cpp', '.h', '.hpp')):
full_path = os.path.join(root, file)
self.parse_file(full_path)
def parse_file(self, filepath):
"""解析單個文件,更新符號表和依賴關(guān)系"""
tu = self.index.parse(filepath, args=['-x', 'c++', '-std=c++11']) # 根據(jù)實際情況調(diào)整編譯參數(shù)
if not tu:
return
# 遍歷所有頂層聲明,收集函數(shù)、變量、類型定義、宏
self._visit_children(tu.cursor, filepath)
# 額外遍歷預處理令牌獲取宏定義(因為宏定義在 AST 中可能不直接作為子節(jié)點出現(xiàn))
self._collect_macros(tu, filepath)
def _visit_children(self, cursor, filepath):
"""遞歸遍歷 AST,收集定義和引用關(guān)系"""
# 處理當前節(jié)點
kind = cursor.kind
location = cursor.location
if location.file and location.file.name != filepath:
# 忽略來自頭文件的節(jié)點,因為該節(jié)點會在解析包含它的源文件時被捕獲
# 但為了確保不遺漏,我們允許跨文件收集,但需要避免重復
pass
# 收集定義
if cursor.is_definition():
if kind == CursorKind.FUNCTION_DECL:
self._add_symbol(cursor, 'function', filepath)
elif kind == CursorKind.VAR_DECL and cursor.is_definition():
# 檢查是否是全局變量(沒有父函數(shù))
if self._is_global(cursor):
self._add_symbol(cursor, 'variable', filepath)
elif kind in (CursorKind.STRUCT_DECL, CursorKind.ENUM_DECL,
CursorKind.TYPEDEF_DECL, CursorKind.CLASS_DECL):
self._add_symbol(cursor, 'type', filepath)
# 注意:宏定義通過 _collect_macros 收集
# 收集函數(shù)體內(nèi)的引用(僅當當前節(jié)點是函數(shù)定義時)
if kind == CursorKind.FUNCTION_DECL and cursor.is_definition():
self._collect_references(cursor)
# 繼續(xù)遍歷子節(jié)點
for child in cursor.get_children():
self._visit_children(child, filepath)
def _is_global(self, cursor):
"""判斷變量是否為全局(沒有父函數(shù))"""
parent = cursor.semantic_parent
while parent:
if parent.kind in (CursorKind.FUNCTION_DECL, CursorKind.CXX_METHOD):
return False
parent = parent.semantic_parent
return True
def _add_symbol(self, cursor, sym_type, filepath):
"""添加符號到符號表,記錄其代碼范圍"""
name = cursor.spelling
if not name:
return
# 如果已存在,保留最早的定義(簡單去重)
if name in self.symbols:
return
start = cursor.extent.start
end = cursor.extent.end
# 提取代碼片段
code = self._extract_code(filepath, start.line, end.line)
self.symbols[name] = {
'type': sym_type,
'file': filepath,
'start_line': start.line,
'end_line': end.line,
'code': code,
'cursor': cursor
}
def _extract_code(self, filepath, start_line, end_line):
"""從文件中提取指定行范圍的代碼"""
if filepath not in self.file_content_cache:
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
self.file_content_cache[filepath] = f.readlines()
except Exception:
return ""
lines = self.file_content_cache[filepath]
# 行號從1開始,轉(zhuǎn)換為索引
start_idx = start_line - 1
end_idx = end_line - 1
return ''.join(lines[start_idx:end_idx+1])
def _collect_references(self, func_cursor):
"""在函數(shù)體內(nèi)收集調(diào)用的其他函數(shù)、使用的全局變量、類型等"""
func_name = func_cursor.spelling
# 遍歷函數(shù)體內(nèi)的所有節(jié)點
def visit_child(cursor):
# 函數(shù)調(diào)用
if cursor.kind == CursorKind.CALL_EXPR:
ref = cursor.referenced
if ref and ref.kind == CursorKind.FUNCTION_DECL:
callee = ref.spelling
if callee and callee != func_name:
self.dependencies[func_name].add(callee)
self.referenced_by[callee].add(func_name)
# 變量引用
elif cursor.kind == CursorKind.DECL_REF_EXPR:
ref = cursor.referenced
if ref and ref.kind == CursorKind.VAR_DECL and self._is_global(ref):
var_name = ref.spelling
if var_name:
self.dependencies[func_name].add(var_name)
self.referenced_by[var_name].add(func_name)
# 類型引用(例如在變量聲明、參數(shù)中)
# 這里簡化處理:通過獲取引用的類型定義來添加依賴
elif cursor.kind in (CursorKind.VAR_DECL, CursorKind.PARM_DECL):
type_cursor = cursor.type.get_declaration()
if type_cursor and type_cursor.kind in (CursorKind.STRUCT_DECL,
CursorKind.ENUM_DECL,
CursorKind.TYPEDEF_DECL,
CursorKind.CLASS_DECL):
type_name = type_cursor.spelling
if type_name:
self.dependencies[func_name].add(type_name)
self.referenced_by[type_name].add(func_name)
# 繼續(xù)遍歷子節(jié)點
for child in cursor.get_children():
visit_child(child)
for child in func_cursor.get_children():
visit_child(child)
def _collect_macros(self, tu, filepath):
"""通過預處理令牌收集宏定義"""
# 獲取所有預處理令牌
tokens = tu.get_tokens(extent=tu.cursor.extent)
current_macro = None
macro_lines = []
macro_start = None
for token in tokens:
if token.kind == TokenKind.PUNCTUATION and token.spelling == '#':
# 遇到 #,可能是宏定義開始
# 簡單處理:下一令牌如果是 'define',則開始收集宏
pass # 這里需要復雜的狀態(tài)機,省略實現(xiàn)細節(jié)
# 實際上 libclang 提供了 CursorKind.MACRO_DEFINITION,但需要遍歷預處理實體?
# 簡單方法:遍歷所有宏定義光標
for cursor in tu.cursor.get_children():
if cursor.kind == CursorKind.MACRO_DEFINITION:
name = cursor.spelling
# 獲取宏定義的范圍
extent = cursor.extent
start = extent.start
end = extent.end
code = self._extract_code(filepath, start.line, end.line)
self.symbols[name] = {
'type': 'macro',
'file': filepath,
'start_line': start.line,
'end_line': end.line,
'code': code,
'cursor': cursor
}
def resolve_dependencies(self):
"""從目標函數(shù)出發(fā),收集所有依賴的符號(廣度優(yōu)先)"""
if self.target_func not in self.symbols:
print(f"錯誤:未找到函數(shù) {self.target_func} 的定義")
sys.exit(1)
visited = set()
queue = deque([self.target_func])
while queue:
sym = queue.popleft()
if sym in visited:
continue
visited.add(sym)
# 獲取該符號的依賴
deps = self.dependencies.get(sym, set())
for dep in deps:
if dep not in visited:
queue.append(dep)
return visited
def topological_sort(self, symbols):
"""對符號集合進行拓撲排序(被依賴的先輸出)"""
# 構(gòu)建子圖
graph = {s: set() for s in symbols}
for s in symbols:
for dep in self.dependencies.get(s, set()):
if dep in symbols:
graph[s].add(dep)
# 計算入度
in_degree = {s: 0 for s in symbols}
for s in symbols:
for dep in graph[s]:
in_degree[dep] += 1
# Kahn 算法
zero_in = deque([s for s in symbols if in_degree[s] == 0])
topo = []
while zero_in:
node = zero_in.popleft()
topo.append(node)
for dep in graph[node]:
in_degree[dep] -= 1
if in_degree[dep] == 0:
zero_in.append(dep)
if len(topo) != len(symbols):
print("警告:存在循環(huán)依賴,將按任意順序輸出")
# 簡單返回原集合
return list(symbols)
return topo
def write_standalone(self, output_file='standalone.cpp'):
"""生成 standalone.cpp 文件"""
# 獲取依賴的所有符號
symbols_to_include = self.resolve_dependencies()
# 按類型分組,以便將類型定義放在前面
type_symbols = [s for s in symbols_to_include if self.symbols[s]['type'] == 'type']
var_symbols = [s for s in symbols_to_include if self.symbols[s]['type'] == 'variable']
func_symbols = [s for s in symbols_to_include if self.symbols[s]['type'] == 'function']
macro_symbols = [s for s in symbols_to_include if self.symbols[s]['type'] == 'macro']
# 拓撲排序:類型和變量之間可能有依賴,但簡單起見,我們先輸出類型,再輸出變量,再輸出函數(shù)
# 更精確的做法是對所有符號一起拓撲排序
all_symbols = list(symbols_to_include)
topo_order = self.topological_sort(all_symbols)
with open(output_file, 'w', encoding='utf-8') as f:
f.write("http:// Standalone generated file\n")
f.write("http:// Include necessary standard headers (you may need to add more)\n")
f.write("#include <stdio.h>\n")
f.write("#include <stdlib.h>\n")
f.write("#include <string.h>\n\n")
# 按照拓撲順序輸出代碼片段
for sym in topo_order:
info = self.symbols[sym]
f.write(f"http:// {info['type']}: {sym} (from {info['file']}:{info['start_line']})\n")
f.write(info['code'])
f.write("\n\n")
print(f"成功生成 {output_file},包含 {len(topo_order)} 個符號。")
def main():
parser = argparse.ArgumentParser(description='提取 C/C++ 函數(shù)及其依賴,生成 standalone.cpp')
parser.add_argument('directory', help='源代碼根目錄')
parser.add_argument('function', help='目標函數(shù)名')
args = parser.parse_args()
extractor = DependencyExtractor(args.directory, args.function)
extractor.parse_files()
extractor.write_standalone()
if __name__ == '__main__':
main()
使用說明
1.安裝依賴:
- 安裝
libclang(例如通過apt install libclang-10-dev)和 Python 綁定clang(pip install clang)。 - 如果
libclang不在默認路徑,需要在代碼開頭設置Config.set_library_path()。
2.運行:
python extract.py /path/to/project my_function
3.輸出:生成 standalone.cpp,包含目標函數(shù)及其依賴的所有函數(shù)、全局變量、類型定義和宏定義,按拓撲順序排列(被依賴的在前)。
注意事項
- 此實現(xiàn)為概念驗證,實際使用需處理更多邊界情況(如函數(shù)重載、命名空間、類成員函數(shù)、模板等)。
- 宏定義的收集依賴預處理令牌,當前代碼未完整實現(xiàn),可能需要改用其他方法(如遍歷預處理實體)。
- 依賴分析僅包含直接的函數(shù)調(diào)用、全局變量引用和類型引用,未考慮通過指針、回調(diào)等間接調(diào)用。
- 代碼提取基于行范圍,可能不準確(例如宏定義跨多行,或函數(shù)體包含大括號嵌套)。
- 對于標準庫函數(shù)(如
printf),不會提取其實現(xiàn),但保留了頭文件包含語句。
如果需要更完善的解決方案,可以考慮使用成熟的工具如 gcc -E 預處理后分析,或基于 clang 的 ASTMatcher。
到此這篇關(guān)于Python代碼實現(xiàn)C++代碼依賴提取工具的文章就介紹到這了,更多相關(guān)Python提取C++代碼依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python實現(xiàn)連續(xù)變量最優(yōu)分箱詳解--CART算法
今天小編就為大家分享一篇python實現(xiàn)連續(xù)變量最優(yōu)分箱詳解--CART算法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11
python字符串,元組,列表,字典互轉(zhuǎn)代碼實例詳解
這篇文章主要介紹了python字符串,元組,列表,字典互轉(zhuǎn)代碼實例詳解,需要的朋友可以參考下2020-02-02
pytorch 實現(xiàn)tensor與numpy數(shù)組轉(zhuǎn)換
今天小編就為大家分享一篇使用pytorch 實現(xiàn)tensor與numpy數(shù)組轉(zhuǎn)換,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
python pandas多數(shù)據(jù)操作的完整指南
在數(shù)據(jù)分析工作中,我們經(jīng)常需要處理多個數(shù)據(jù)集并將它們以各種方式組合起來,Pandas 提供了多種強大的多數(shù)據(jù)操作方法,下面就跟隨小編一起了解一下吧2025-04-04
Python gmpy2實現(xiàn)高性能多重精度計算的終極指南
gmpy2是一個為Python提供多重精度算術(shù)功能的強大擴展庫,無論您是需要處理超大整數(shù)運算,還是進行高精度浮點數(shù)計算,gmpy2都能提供遠超Python標準庫的性能表現(xiàn),下面我們就來看看它的具體使用吧2025-12-12
Python PyQt5實戰(zhàn)項目之文件拷貝器的具體實現(xiàn)詳解
PyQt5以一套Python模塊的形式來實現(xiàn)功能。它包含了超過620個類,600個方法和函數(shù)。本篇文章手把手帶你用PyQt5實現(xiàn)一個簡單的文件拷貝器,大家可以在過程中查缺補漏,提升水平2021-11-11
Python循環(huán)語句For?Range用法示例詳解
這篇文章主要為大家介紹了Python循環(huán)語句For?Range用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09

