提问人:Dmitry Kuzminov 提问时间:11/11/2022 最后编辑:Dmitry Kuzminov 更新时间:11/14/2022 访问量:435
堆垛放卷背后的机制是什么?
What is the machinery behind stack unwinding?
问:
我试图理解 C++ 中堆栈展开背后的机制。换句话说,我对这个功能是如何实现的(以及这是否是标准的一部分)感兴趣。
因此,线程会执行一些代码,直到引发异常。当抛出异常时,用于记录状态和展开堆栈的线程/中断处理程序是什么?标准保证了什么,什么是具体实施的?
答:
1赞
MSalters
11/14/2022
#1
线程执行一些代码,直到引发异常,并继续执行此操作。异常处理仍然是 C++ 代码。
表达式创建一个 C++ 对象,在抛出函数的上下文中运行。当异常对象的构造函数正在运行时,抛出函数范围内的所有对象仍处于活动状态。throw
然而,紧接着,堆栈展开就发生了。C++ 编译器将安排一个不需要返回对象但允许传递异常对象的返回路径。就像正常返回一样,当函数返回时,函数的本地对象将被销毁。在二进制级别,这非常简单:它只是一堆析构函数调用,通常还会调整堆栈指针。
该标准也没有具体说明用于确定需要退出多少范围的机制。该标准以 来描述,但典型的 CPU 没有直接的等价物。因此,这通常是给定平台的 C++ ABI 的一部分,以便共享 ABI 的编译器同意。ABI 兼容性要求调用方必须捕获来自被调用方的异常,即使这些异常是使用不同的编译器编译的。显然,析构函数必须被调用,因此 ABI 也需要安排该机制。中间函数甚至可以由第三个编译器编译 - 只要它们共享一个 ABI,它都应该工作。catch
如注释中所述,C++没有中断的概念。如果操作系统需要对中断进行某些事情,编译器需要处理。当时C++代码到底在做什么并不重要。
评论
0赞
Mike Nakis
11/20/2022
我认为文本中有一个稍微不准确的陈述:它不完全是允许传递异常对象的返回路径;这听起来好像该函数能够交替返回异常对象,而我认为这实际上并非如此。堆栈中每个函数的清理代码由堆栈展开代码调用,但它不会接收或返回异常对象。 子句接收它,但同样,它们不会返回它。catch
0赞
Mike Nakis
11/20/2022
可以举几个例子来说明堆栈展开代码如何检测子句。一种方法是始终将一个特殊的 sentinel 值推送到堆栈中,并将其弹出到子句的末尾。然后,堆栈展开代码只需要查看堆栈中返回地址之前的值。catch
finally
try
try-catch-finally
0赞
Mike Nakis
11/20/2022
另一种方法是始终遵循每条指令,并相对跳转几个字节,并将哨兵值存储在这些字节中。这在正常情况下表现稍好一些,但在堆栈展开期间需要更多的工作,因为堆栈展开器现在需要查看返回地址处的代码,但这通常没问题。call
评论