C++如何动态构建指定被追踪函数的调用关系图?
实现核心思路
用线程局部存储(TLS)维护当前调用栈中最近的已追踪节点指针,配合RAII类型的宏自动处理节点注册、父节点绑定、当前节点栈的进出,不需要手动解析调用栈即可自动获取父节点。
第一步:实现核心工具代码
#include <string> #include <vector> #include <mutex> #include <stack> #include <iostream> #include <memory> // 调用图节点结构 struct CallNode { std::string funcName; std::vector<std::shared_ptr<CallNode>> children; CallNode* parent = nullptr; explicit CallNode(std::string name) : funcName(std::move(name)) {} }; // 单例调用图存储 class CallGraph { public: static CallGraph& instance() { static CallGraph inst; return inst; } // 注册新节点,返回节点指针 CallNode* registerNode(const std::string& funcName, CallNode* parent) { std::lock_guard<std::mutex> lock(mtx); auto node = std::make_shared<CallNode>(funcName); node->parent = parent; if (parent) { parent->children.push_back(node); } else { rootNodes.push_back(node); } return node.get(); } // 按要求格式打印调用图 void print() const { std::lock_guard<std::mutex> lock(mtx); for (const auto& root : rootNodes) { printNode(root, 0); } } private: CallGraph() = default; mutable std::mutex mtx; std::vector<std::shared_ptr<CallNode>> rootNodes; void printNode(const std::shared_ptr<CallNode>& node, int depth) const { if (depth == 0) { std::cout << node->funcName << "\n"; } else { for (int i = 0; i < depth - 1; ++i) std::cout << " "; std::cout << "\\-- " << node->funcName << "\n"; } for (const auto& child : node->children) { printNode(child, depth + 1); } } }; // 线程局部存储:隔离不同线程的追踪栈,避免调用关系串扰 thread_local std::stack<CallNode*> tls_trackStack; // RAII追踪守卫:函数进入时注册节点、入栈,函数退出时自动出栈 class TrackGuard { public: // 编译器内置宏自动获取带模板参数的完整函数名,MSVC环境替换为__FUNCSIG__即可 explicit TrackGuard(const char* funcName) { CallNode* parent = tls_trackStack.empty() ? nullptr : tls_trackStack.top(); m_curNode = CallGraph::instance().registerNode(funcName, parent); tls_trackStack.push(m_curNode); } ~TrackGuard() { if (!tls_trackStack.empty() && tls_trackStack.top() == m_curNode) { tls_trackStack.pop(); } } private: CallNode* m_curNode = nullptr; }; // 追踪标记宏:需要追踪的函数第一行加此宏即可 #define TRACK_THIS_FUNC() TrackGuard __track_guard(__PRETTY_FUNCTION__)
第二步:修改业务代码添加追踪标记
只需要在需要追踪的函数第一行加TRACK_THIS_FUNC(),无需修改其他业务逻辑:
template <class TA> class A { public: void runTracked() { TRACK_THIS_FUNC(); // do stuff } }; template <class TB> class B { public: void runNonTracked() { // 不加宏就不会被追踪 // do stuff } void runTracked() { TRACK_THIS_FUNC(); // do stuff } void callATracked() { TRACK_THIS_FUNC(); auto a = A<TB>(); a.runTracked(); // do stuff } }; void root() { TRACK_THIS_FUNC(); // 自定义根入口加标记就会成为根节点 auto b1 = B<int>(); auto b2 = B<double>(); b1.runTracked(); b2.runNonTracked(); b2.callATracked(); } int main() { auto b = B<int>(); b.runTracked(); // 没有加宏的函数里调用的追踪方法,父节点为空会成为独立根节点 root(); // 程序退出前打印调用图 CallGraph::instance().print(); return 0; }
方案特性
- 自动识别父节点:线程栈自动维护最近的已追踪节点,完全不需要手动解析调用栈
- 灵活调整追踪范围:加/删
TRACK_THIS_FUNC()宏即可调整需追踪的函数 - 支持模板类/函数:编译器内置宏自动带出模板参数,输出格式完全匹配要求
- 多线程安全:线程局部存储隔离不同线程的调用栈,加锁保证单例线程安全
- 无业务侵入:仅添加一行标记,不修改原有业务逻辑
内容的提问来源于stack exchange,提问作者Gábor Pálovics
相关产品推荐
相关产品推荐

