Visual c++ 由于ntdll,在重试异常时发生堆栈溢出!RcConsolidateFrame(x64)
我正在努力处理堆栈溢出异常,该异常在重新触发另一个异常时发生。rethrown异常用于在递归函数调用自身超过一定次数后拆除调用堆栈。(防止发生堆栈溢出) 我设法写了一个小程序,再现了这个问题。只有当我使用x64(发布和调试)编译程序时,才会出现这种情况。Visual c++ 由于ntdll,在重试异常时发生堆栈溢出!RcConsolidateFrame(x64),visual-c++,64-bit,try-catch,stack-overflow,rethrow,Visual C++,64 Bit,Try Catch,Stack Overflow,Rethrow,我正在努力处理堆栈溢出异常,该异常在重新触发另一个异常时发生。rethrown异常用于在递归函数调用自身超过一定次数后拆除调用堆栈。(防止发生堆栈溢出) 我设法写了一个小程序,再现了这个问题。只有当我使用x64(发布和调试)编译程序时,才会出现这种情况。 我用MSVS2012和MSVS2013测试了这个代码段(StackSize=1MB-默认值)。 对于G++来说,在大约5000次调用之后,同样的问题也会发生 代码: 注:框架尺寸位于下一行的第二列 ntdll的框架!RcConsolidateF
我用MSVS2012和MSVS2013测试了这个代码段(StackSize=1MB-默认值)。 对于G++来说,在大约
5000次调用之后,同样的问题也会发生
代码:
注:框架尺寸位于下一行的第二列
ntdll的框架!RcConsolidateFrames
的大小为0xf7970
(1.014.128)字节,因此占1MB总可用堆栈大小的96%
最让我头疼的是,在调用堆栈用完之前,我可以(如剪报中所述)递归调用函数近10.600次,而另一次调用会导致堆栈溢出。但是,如果我在超过120次调用后使用exoption中止递归,我将再次得到堆栈溢出,这是此异常旨在防止的。
因此,使用更大的堆栈编译程序只会将问题转移到略高的常量。
如前所述,此问题仅在x64编译时出现。如果使用Win32编译,一旦抛出std::exception
,程序就不会遇到堆栈溢出
在堆栈展开过程中分配的堆栈空间比释放的堆栈空间多,程序有什么意义?
我如何解决这个问题?
由于这只是一个非常简化的情况,我不能简单地用原始应用程序中的特殊返回值替换抛出
编辑:Microsoft只是删除了该请求,没有提供任何帮助。我收到了以下答复,要求提供更多细节,但在我答复后一天,该请求被删除
谢谢你报道这个问题。虽然大堆栈使用率并不理想,但它是如何在Windows非x86平台上实现EH的一个重要结果。需要注意的一点是,RcConsolidateFrames下的堆栈用法有点误导。该函数的要点是使展开器隐藏一组中间帧,以便堆栈使用情况反映运行的EH机制的105个实例(每个执行的重试一个实例)以及所有递归子调用
您能否分享更多关于这种高堆栈使用正在阻止的真实场景的信息?解决这一问题可能并不容易,但如果我们能够对重要的场景做出一些简化假设,就有可能改进这一点
谢谢,
Neeraj Singh VC++编译器后端开发人员
同时,尽管我希望,问题将在于重新引用机制,它一次又一次地重新引用在堆栈上分配的相同异常对象。我想是其中一个
- 复制异常并引发异常的副本
- 正在引发异常,使用
new
无法解决此问题,但结果没有差异。的异常模型与x86的异常模型大不相同。您说过这是一个简单的例子,但我很难想象递归抛出异常可能是多么明智的设计。@CodyGray实际应用程序是一个语言解释器。如果解释的代码包含太多嵌套调用,无法释放调用堆栈并将控制权优雅地返回到顶级执行事件处理程序,则会引发异常。@HansPassant感谢对\u resetstkoflw
的提示,我至少知道如何从堆栈溢出中恢复。您能否更具体地说明8KB的堆栈保留空间,以及这是什么意思?在解开105个调用堆栈后,我希望有超过8KB的可用空间。你是说可以递归调用的函数不能包含try-catch/rethrow吗?这似乎是对语言的严重限制。@HansPassant好的,所以我的代码在出现这种情况时没有展开任何帧。即使每个被调用函数的堆栈帧仍然在堆栈上,我也不明白为什么堆栈会溢出,因为在调用堆栈因递归调用而溢出之前,我最多可以调用递归函数10.600次。因此,当堆栈上有120个调用帧时,堆栈应最多使用2%。为什么在调用堆栈中重新调用异常时会发生如此显著的变化?所有这些空间都是用来做什么的?对不起,我一直在发布非常误导性的评论,太关注SO了。它似乎很难在嵌套帧中活动的catch子句之后展开帧。没有太多的合并。不漂亮。在VS2017中仍然是一个问题,因此从技术上讲,Microsoft支持可以帮助您深入挖掘根本问题。如果不是紧急情况,请使用connect.microsoft.com。
#include <iostream>
using namespace std;
void recursiveFunction(int childCalls) {
cout << "Recursive call, left calls: " << childCalls << endl;
if (childCalls == 0) {
cout << "Throwing std::exception" << endl;
throw std::exception("Target depth reached");
}
try {
recursiveFunction(childCalls - 1);
} catch (std::exception&) {
cout << "Caught exception at level: " << childCalls << endl;
throw; //Simply rethrow exception
}
}
int main() {
//How many calls cause a stack overflow during unwinding with x64
const int calls = 120;
//How many recursive calls I can make before the call stack overflows due to recursive calls
//const int calls = 10600;
cout << "Initiating " << calls << " recursive calls" << endl;
recursiveFunction(calls);
}
Initiating 120 recursive calls
Recursive call, left calls: 120
Recursive call, left calls: 119
Recursive call, left calls: 118
...
Recursive call, left calls: 2
Recursive call, left calls: 1
Recursive call, left calls: 0
Throwing std::exception
Caught exception at level: 1
Caught exception at level: 2
Caught exception at level: 3
...
Caught exception at level: 104
Caught exception at level: 105 <-- Stack overflow here!!!
0:000> knf
# Memory Child-SP RetAddr Call Site
00 00000065`d5c37260 00007ffc`5ac10658 MSVCR110D!_chkstk+0x37
01 18 00000065`d5c37278 00007ffc`5ac105bf MSVCR110D!write_nolock+0x18
02 8 00000065`d5c37280 00007ffc`5ab23db1 MSVCR110D!write+0x21f
...
0a f0 00000065`d5c37710 00007ffc`5ac09150 Crashtest!`recursiveFunction'::`1'::catch$0+0x26
0b 40 00000065`d5c37750 00007ffc`5abf93f2 MSVCR110D!CallSettingFrame+0x20
0c 30 00000065`d5c37780 00007ffc`7dd3a193 MSVCR110D!_CxxCallCatchBlock+0x162
0d a0 00000065`d5c37820 00007ff7`f14714ca ntdll!RcConsolidateFrames+0x3
0e f7970 00000065`d5d2f190 00007ff7`f14714ca Crashtest!recursiveFunction+0xba
0f 60 00000065`d5d2f1f0 00007ff7`f14714ca Crashtest!recursiveFunction+0xba
...