C++ 23 的栈踪迹库(stacktrace)

news/2025/2/9 5:44:17 标签: c++23, c++, stacktrace, 栈踪迹

1 Boost.Stacktrace

​ 当程序发生错误的时候,能提供的信息越多,对错误的定位就越有利。C#、Pyrhon、Java 等编程语言都提供调用栈踪迹回溯的功能,在错误发生的时候,除了报告错误发生的位置,还能输出函数调用栈信息。但是 C++ 无论语言特性还是库,都不支持调用栈的回溯,当错误发生的时候,通过传统的宏或 C++ 20 的 std::source_location,也只能得到错误发生时的位置信息,当这个位置存在于多个函数调用链上的时候,就无法得知错误发生的源头,对错误的定位非常不利。

​ 有很多第三方的调试支持库提供栈回溯功能,比如 Boost.Stacktrace 库,这是使用 boost 库输出函数调用栈的例子:

#include <boost/stacktrace.hpp>

using namespace bst = boost::stacktrace;

void Test{
    std::cout << bst::stacktrace();
}

int main() {
    Test();
}

作为 C++ 标准库的技术储备库,许多支持者希望将 Boost.Stacktrace 转正。不过也有一些反对的声音,主要是两点,其一是性能,大家希望 Boost.Stacktrace 能够快一点,但是 Boost.Stacktrace 的实现是在调用 stacktrace() 的时候就将整个调用栈全部解码成可读的字符串,在很多人看来没必要这么做,在需要输出可读信息的时候再做这个解码才是合理的。另一个问题是存储信息量的问题。 Boost.Stacktrace 的实现不依赖任何标准库的组件,所以也没有使用分配器,它使用一个固定大小的存储区存储栈的信息,这对导致调用栈比较深的时候,调用栈低端的一些重要信息无法存下来。

stacktrace_22">2 std::stacktrace

2.1 基础结构

​ P0881R7 提案建议 C++ 支持栈踪迹库,其设计就是基于 Boost.Stacktrace 库,但是很好地解决了上面提到的两个问题。最终提案被 C++ 接纳,成为 C++ 23 标准的一部分。C++ 栈踪迹库的基础结构是 std::basic_stacktrace 类模板,stacktrace 是使用标准库的默认内存分配器的一个类型别名:

template <class _Alloc>
class basic_stacktrace { ... }

using stacktrace = basic_stacktrace<allocator<stacktrace_entry>>;

默认的 allocator 是在堆上分配调用栈信息,对安全性要求很高的场合,用户可以提供定制的 allocator,在栈上分配空间存储调用栈信息。类似前面 Boost 库的例子,直接输出当前调用栈信息的方法是:

std::cout << std::stacktrace::current();

​ std::basic_stacktrace 类的静态成员函数 current() 返回一个包含完整调用栈信息的 std::basic_stacktrace<> 对象实例,此时其中的信息还依赖于操作系统的内部接口,可读信息的解码是在调用重载的流输出操作符时才进行的,这就是这个库在设计上所提到的 lazy 模式。

stacktrace_entry_43">2.2 std::stacktrace_entry

​ std::stacktrace_entry 代表的是每层(Frame)调用栈的信息,我们可以通过遍历 basic_stacktrace 对象获取每一层的信息,也可以通过 basic_stacktrace 的 at() 成员函数或下标运算符重载获得对应层的信息:

std::stacktrace stack = std::stacktrace::current();
const std::stacktrace_entry& ste = stack[0];

std::cout << ste.description() << std::endl;
std::cout << ste.source_file() << std::endl;
std::cout << ste.source_line() << std::endl;

stacktrace_56">2.3 遍历 std::stacktrace

​ std::stacktrace 提供了 begin() 和 end(),可以通过它们返回的迭代器遍历调用栈信息:

std::stacktrace stack = std::stacktrace::current();

for (auto it = stack.begin(); it != stack.end(); ++it)
    std::cout << *it << std::endl;

std::stacktrace 还提供了 size() 函数和下标运算符重载,可以通过索引遍历调用栈信息:

auto stack = std::stacktrace::current();

for(std::size_t i = 0; i < stack.size(); i++)
    std::cout << stack[i] << std::endl;

当然,最“甜”的方法就是直接用 for 循环遍历:

auto stack = std::stacktrace::current();

for(auto&& entry : stack)
    std::cout << entry << std::endl;

2.4 字符串与输出

​ 前面的例子代码中,我们可以用流输运算符直接输出 std::stacktrace 和 std::stacktrace_entry,是因为这两个类都提供了流输出运算符的重载。当然,这两个类也提供了 to_string() 函数的重载,可以利用 to_string() 直接得到调用栈信息的字符串,比如:

auto stack = std::stacktrace::current();
const std::stacktrace_entry& ste = stack[0];

//../../../soucr.cpp(172): NothingFind + 0x168
std::cout << std::to_string(ste) << std::endl;

3 C++ 26 的展望

​ 当异常发生的时候,在异常捕捉的位置能获得异常发生时的函数调用栈信息吗?试着运行一下下面的代码?

void foo() {
    throw std::runtime_error("foo failed");
}

try {
    foo();
}
catch (const std::exception& e) {
    std::cerr << std::stacktrace::current() << std::endl;
}

结果得到的是异常处理位置的调用栈,不是我们希望的 foo() 函数中扔出异常的位置。cpptrace 库[资料 5] 有一个非常实用的功能,它提供了一个 from_current_exception() 函数,用于在异常捕捉位置获取异常发生时的函数调用栈信息。与其对 cpptrace 库流口水,不如赶紧给 C++ 上提案,P2370R0 [资料 6] 提出 C++ 也要提供一个 from_current_exception() 函数,用于在异常处理位置捕获函数调用栈信息。P2370 系列提案最大的问题就是会给异常处理部分增加额外的开销,这显著违反了一个重要的 C++ 原则,即“当你不需要的时候,你不必为此付出代价”。

​ P2490 [资料 3] 系列提案提出的一种零开销方案,就是增加一个 [[with_stacktrace]] 属性说明符,用于指示编译器在该位置保留异常发生时的函数调用栈信息,如下例子所示:

try {
    ...
} catch ([[with_stacktrace]] std::exception& e) {
    std::cout << std::stacktrace::from_current_exception() << std::endl;
}

如果用户没有在 catch 语句中使用这个说明符,则编译器不会保留异常发生时的函数调用栈信息,也就没有额外的开销。不过 P2490 目提案在 2022 年 6 月提出 P2490R3 版本之后就没有什么动作了,看起来进入 C++ 26 的希望有点渺茫。

4 总结

stacktrace__132">4.1 C++ 23 stacktrace 库的特点

​ C++ 的 stacktrace 库结构简单,使用也简单,主要特点是:

  • 所有的 stacktrace_entry 都是延迟绑定,只有调用 to_string() 或 operator << 时才对栈帧信息解码;
  • 栈帧信息采用动态存储,最重要的底部栈帧信息也会存储
  • 简单

简单如果也算的话,那就是三个特点,这里再说说第二条。前面其实也提过,因为 C++ 的 stacktrace 库一般情况下使用标准库的默认分配器,也就是动态分配在堆上。使用堆内存可能的问题就是性能问题,对于一些性能优先的函数调用路径上使用 C++ 的 stacktrace 库要非常慎重。从性能方面考虑,可以考虑用 tcmalloc 之类的第三方库提供的分配器代替标准库的默认分配器。从安全方面考虑,也可以构造在栈上分配空间的特殊分配器代替默认分配器,这个前面也提到过了。

4.2 其他第三方库

​ 如果你的编译器还不支持 stacktrace 库,你可以使用 Boost.Stacktrace 库,当然,还有很多优秀的库可以选择,比如 backward-cpp,cpptrace 等等。

参考资料

[1] https://github.com/boostorg/stacktrace

[2] P0881R7: A Proposal to add stacktrace library

[3] P2490R3: Zero-overhead exception stacktraces (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2490r3.html)

[4] https://github.com/bombela/backward-cpp

[5] https://github.com/jeremy-rifkin/cpptrace

[6] P2370R0: Stacktrace from exception (https://www9.open-std.org/JTC1/SC22/WG21/docs/papers/2021/p2370r0.html)

[7] https://github.com/jeremy-rifkin/cpptrace?tab=readme-ov-file#traces-from-all-exceptions

关注作者的算法专栏
https://blog.csdn.net/orbit/category_10400723.html

关注作者的出版物《算法的乐趣(第二版)》
https://www.ituring.com.cn/book/3180


http://www.niftyadmin.cn/n/5845606.html

相关文章

大模型推理——MLA实现方案

1.整体流程 先上一张图来整体理解下MLA的计算过程 2.实现代码 import math import torch import torch.nn as nn# rms归一化 class RMSNorm(nn.Module):""""""def __init__(self, hidden_size, eps1e-6):super().__init__()self.weight nn.Pa…

Qt实现简易视频播放器

使用Qt6实现简易音乐播放器&#xff0c;效果如下&#xff1a; github&#xff1a; Gabriel-gxb/VideoPlayer: qt6实现简易视频播放器 一、整体架构 该代码整体架构围绕着MainWindow类构建一个媒体播放器相关的应用程序。 主要组件 &#xff08;一&#xff09;界面组件&…

携手AWS,零成本在EKS上体验AutoMQ企业版

01 前言 AutoMQ是一款贯彻云优先理念来设计的 Kafka 替代产品。AutoMQ 创新地对 Apache Kafka 的存储层进行了基于云的重新设计&#xff0c;在 100% 兼容 Kafka 的基础上通过将持久性分离至 EBS 和 S3 带来了 10x 的成本降低以及 100x 的弹性能力提升&#xff0c;并且相比 Apa…

springcloud gateway 负载均衡

Spring Cloud Gateway的负载均衡是Spring Cloud生态系统中一个非常重要的功能&#xff0c;它使得微服务架构中的服务调用能够更加高效和均衡。以下是关于Spring Cloud Gateway负载均衡的详细解析&#xff1a; 一、Spring Cloud Gateway简介 Spring Cloud Gateway是一个基于Sp…

使用 Apifox、Postman 测试 Dubbo 服务,Apache Dubbo OpenAPI 即将发布

作者&#xff1a;何亮&#xff0c;Apache Dubbo Contributor Apache Dubbo OpenAPI 简介 设计背景 在微服务体系中&#xff0c;RPC 服务的文档管理、测试、调用协作一直都是影响研发效能的关键一环&#xff0c;这些难题通常是由于 RPC 的特性所决定的&#xff1a;RPC 服务的…

SpringSecurity:授权服务器与客户端应用(入门案例)

文章目录 一、需求概述二、开发授权服务器1、pom依赖2、yml配置3、启动服务端 三、开发客户端应用1、pom依赖2、yml配置3、SecurityConfig4、接口5、测试 一、需求概述 maven需要3.6.0以上版本 二、开发授权服务器 1、pom依赖 <dependency><groupId>org.springfr…

leetcode_深度遍历和广度遍历 100. 相同的树

100. 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两棵树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 思路: (递归法) 返回True的情况: 两棵树都为空两棵树相同 返回False的情况: 两棵…

知识图谱智能应用系统:数据存储架构与流程解析

在当今数字化时代,知识图谱作为一种强大的知识表示和管理工具,正逐渐成为企业、科研机构以及各类智能应用的核心技术。知识图谱通过将数据转化为结构化的知识网络,不仅能够高效地存储和管理海量信息,还能通过复杂的查询和推理,为用户提供深度的知识洞察。然而,构建一个高…