Python代碼實(shí)現(xiàn)C++代碼依賴提取工具
編寫一個(gè)Python程序代碼,輸入C/C++源文件名和函數(shù)名字符串,解析一個(gè)目錄下所有C/C++的源代碼,找到相關(guān)的宏名、變量名和函數(shù)名對(duì)應(yīng)的具體實(shí)現(xiàn)代碼,以及和它們相關(guān)的具體實(shí)現(xiàn)代碼,按依賴性順序?qū)懭胍粋€(gè)standalone.cpp的文件里。
下面是一個(gè)使用 libclang 提取 C/C++ 函數(shù)及其依賴并生成 standalone.cpp 的 Python 程序框架。由于完整實(shí)現(xiàn)非常復(fù)雜,這里給出了核心思路和關(guān)鍵代碼,實(shí)際使用時(shí)需要根據(jù)項(xiàng)目情況調(diào)整。
import os
import sys
import argparse
from collections import defaultdict, deque
from clang.cindex import Index, CursorKind, TokenKind, Config
# 如果 libclang 不在默認(rèn)路徑,需要設(shè)置 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 = {} # 符號(hào)名 -> 符號(hào)信息(類型、定義位置、代碼片段等)
self.dependencies = defaultdict(set) # 符號(hào)名 -> 依賴的符號(hào)名集合
self.referenced_by = defaultdict(set) # 反向依賴,用于拓?fù)渑判?
self.file_content_cache = {} # 文件內(nèi)容緩存,用于提取代碼片段
def parse_files(self):
"""遍歷目錄下所有 .c/.cpp/.h/.hpp 文件,解析并收集符號(hào)定義和引用關(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):
"""解析單個(gè)文件,更新符號(hào)表和依賴關(guān)系"""
tu = self.index.parse(filepath, args=['-x', 'c++', '-std=c++11']) # 根據(jù)實(shí)際情況調(diào)整編譯參數(shù)
if not tu:
return
# 遍歷所有頂層聲明,收集函數(shù)、變量、類型定義、宏
self._visit_children(tu.cursor, filepath)
# 額外遍歷預(yù)處理令牌獲取宏定義(因?yàn)楹甓x在 AST 中可能不直接作為子節(jié)點(diǎn)出現(xiàn))
self._collect_macros(tu, filepath)
def _visit_children(self, cursor, filepath):
"""遞歸遍歷 AST,收集定義和引用關(guān)系"""
# 處理當(dāng)前節(jié)點(diǎn)
kind = cursor.kind
location = cursor.location
if location.file and location.file.name != filepath:
# 忽略來自頭文件的節(jié)點(diǎn),因?yàn)樵摴?jié)點(diǎn)會(huì)在解析包含它的源文件時(shí)被捕獲
# 但為了確保不遺漏,我們?cè)试S跨文件收集,但需要避免重復(fù)
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)的引用(僅當(dāng)當(dāng)前節(jié)點(diǎn)是函數(shù)定義時(shí))
if kind == CursorKind.FUNCTION_DECL and cursor.is_definition():
self._collect_references(cursor)
# 繼續(xù)遍歷子節(jié)點(diǎn)
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):
"""添加符號(hào)到符號(hào)表,記錄其代碼范圍"""
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]
# 行號(hào)從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é)點(diǎn)
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é)點(diǎn)
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):
"""通過預(yù)處理令牌收集宏定義"""
# 獲取所有預(yù)處理令牌
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 # 這里需要復(fù)雜的狀態(tài)機(jī),省略實(shí)現(xiàn)細(xì)節(jié)
# 實(shí)際上 libclang 提供了 CursorKind.MACRO_DEFINITION,但需要遍歷預(yù)處理實(shí)體?
# 簡單方法:遍歷所有宏定義光標(biāo)
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):
"""從目標(biāo)函數(shù)出發(fā),收集所有依賴的符號(hào)(廣度優(yōu)先)"""
if self.target_func not in self.symbols:
print(f"錯(cuò)誤:未找到函數(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)
# 獲取該符號(hào)的依賴
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):
"""對(duì)符號(hào)集合進(jìn)行拓?fù)渑判颍ū灰蕾嚨南容敵觯?""
# 構(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)
# 計(jì)算入度
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 文件"""
# 獲取依賴的所有符號(hào)
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']
# 拓?fù)渑判颍侯愋秃妥兞恐g可能有依賴,但簡單起見,我們先輸出類型,再輸出變量,再輸出函數(shù)
# 更精確的做法是對(duì)所有符號(hào)一起拓?fù)渑判?
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")
# 按照拓?fù)漤樞蜉敵龃a片段
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)} 個(gè)符號(hào)。")
def main():
parser = argparse.ArgumentParser(description='提取 C/C++ 函數(shù)及其依賴,生成 standalone.cpp')
parser.add_argument('directory', help='源代碼根目錄')
parser.add_argument('function', help='目標(biāo)函數(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不在默認(rèn)路徑,需要在代碼開頭設(shè)置Config.set_library_path()。
2.運(yùn)行:
python extract.py /path/to/project my_function
3.輸出:生成 standalone.cpp,包含目標(biāo)函數(shù)及其依賴的所有函數(shù)、全局變量、類型定義和宏定義,按拓?fù)漤樞蚺帕校ū灰蕾嚨脑谇埃?/p>
注意事項(xiàng)
- 此實(shí)現(xiàn)為概念驗(yàn)證,實(shí)際使用需處理更多邊界情況(如函數(shù)重載、命名空間、類成員函數(shù)、模板等)。
- 宏定義的收集依賴預(yù)處理令牌,當(dāng)前代碼未完整實(shí)現(xiàn),可能需要改用其他方法(如遍歷預(yù)處理實(shí)體)。
- 依賴分析僅包含直接的函數(shù)調(diào)用、全局變量引用和類型引用,未考慮通過指針、回調(diào)等間接調(diào)用。
- 代碼提取基于行范圍,可能不準(zhǔn)確(例如宏定義跨多行,或函數(shù)體包含大括號(hào)嵌套)。
- 對(duì)于標(biāo)準(zhǔn)庫函數(shù)(如
printf),不會(huì)提取其實(shí)現(xiàn),但保留了頭文件包含語句。
如果需要更完善的解決方案,可以考慮使用成熟的工具如 gcc -E 預(yù)處理后分析,或基于 clang 的 ASTMatcher。
到此這篇關(guān)于Python代碼實(shí)現(xiàn)C++代碼依賴提取工具的文章就介紹到這了,更多相關(guān)Python提取C++代碼依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python實(shí)現(xiàn)連續(xù)變量最優(yōu)分箱詳解--CART算法
今天小編就為大家分享一篇python實(shí)現(xiàn)連續(xù)變量最優(yōu)分箱詳解--CART算法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11
python字符串,元組,列表,字典互轉(zhuǎn)代碼實(shí)例詳解
這篇文章主要介紹了python字符串,元組,列表,字典互轉(zhuǎn)代碼實(shí)例詳解,需要的朋友可以參考下2020-02-02
Python還能這么玩之用Python修改了班花的開機(jī)密碼
今天帶大家學(xué)習(xí)如何用Python修改開機(jī)密碼,文中有非常詳細(xì)的代碼示例,喜歡惡作劇的小伙伴可以看一下,不過不要亂用哦,需要的朋友可以參考下2021-06-06
pytorch 實(shí)現(xiàn)tensor與numpy數(shù)組轉(zhuǎn)換
今天小編就為大家分享一篇使用pytorch 實(shí)現(xiàn)tensor與numpy數(shù)組轉(zhuǎn)換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-12-12
python pandas多數(shù)據(jù)操作的完整指南
在數(shù)據(jù)分析工作中,我們經(jīng)常需要處理多個(gè)數(shù)據(jù)集并將它們以各種方式組合起來,Pandas 提供了多種強(qiáng)大的多數(shù)據(jù)操作方法,下面就跟隨小編一起了解一下吧2025-04-04
Python gmpy2實(shí)現(xiàn)高性能多重精度計(jì)算的終極指南
gmpy2是一個(gè)為Python提供多重精度算術(shù)功能的強(qiáng)大擴(kuò)展庫,無論您是需要處理超大整數(shù)運(yùn)算,還是進(jìn)行高精度浮點(diǎn)數(shù)計(jì)算,gmpy2都能提供遠(yuǎn)超Python標(biāo)準(zhǔn)庫的性能表現(xiàn),下面我們就來看看它的具體使用吧2025-12-12
Python PyQt5實(shí)戰(zhàn)項(xiàng)目之文件拷貝器的具體實(shí)現(xiàn)詳解
PyQt5以一套Python模塊的形式來實(shí)現(xiàn)功能。它包含了超過620個(gè)類,600個(gè)方法和函數(shù)。本篇文章手把手帶你用PyQt5實(shí)現(xiàn)一個(gè)簡單的文件拷貝器,大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11
Python循環(huán)語句For?Range用法示例詳解
這篇文章主要為大家介紹了Python循環(huán)語句For?Range用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
python實(shí)現(xiàn)跨年表白神器--你值得擁有
這篇文章主要介紹了python實(shí)現(xiàn)跨年表白神器的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-01-01

