我想知道为什么在以下两个代码段中为printf的cout和Stack Overflow发生访问冲突。
我想知道为什么第一个代码的Access Violation而不是Stack Overflow。
我遇到访问冲突的第一个代码:
void Test();
void Test()
{
static int i = 0;
cout << i++ << endl;
Test();
}
int main()
{
Test();
return 0;
}
我获得堆栈溢出的第二个代码:
void Test();
void Test()
{
static int i = 0;
printf("%d\n", i++);
Test();
}
int main()
{
Test();
return 0;
}
答案将受到高度赞赏。
提前致谢
答案 0 :(得分:18)
我假设你理解这两个函数在尝试无限递归后由于堆栈耗尽而崩溃。我想你要问的是:为什么cout示例不会因“Stack Overflow”而崩溃?
我不认为答案与编译器检测尾递归有关。如果编译器优化了递归,那么示例都不会崩溃。
我猜测发生了什么。在某些情况下(例如,Windows)实现“堆栈溢出”异常,其中在堆栈的末尾分配单个虚拟存储器“保护页面”。当堆栈访问命中该保护页面时,会生成一个特殊的异常类型。
由于英特尔小粒度页面的长度为4096字节,因此保护页面可以防范大小的内存范围。如果一个函数调用分配了超过4096个字节的局部变量,那么它的第一个堆栈访问可能实际上将拉伸到保护页面之外。可以预期下一页是无保留的内存,因此在这种情况下访问违规是有意义的。
当然,您没有在示例中显式声明任何局部变量。我假设其中一个运算符&lt;&lt;()方法分配了多个局部变量页面。换句话说,访问冲突发生在运算符&lt;&lt;()方法的开头附近或cout实现的某些其他部分(临时对象构造函数等)
此外,即使在您编写的函数中,运算符&lt;&lt;()实现也需要为中间结果创建一些存储。该存储可能由编译器分配为本地存储。我怀疑它会在你的例子中加起来达到4k。
真正理解的唯一方法是查看访问冲突的堆栈跟踪,以查看触发它的指令。
是否存在访问冲突的堆栈跟踪以及错误操作码区域周围的反汇编?
如果您使用的是Microsoft C编译器,另一种可能性是printf()和您自己的函数是用/ Ge编译的,而运算符&lt;&lt;()则不是,或者只有您的函数是用/ Ge和因子编译的类似于上面描述的那些巧合会导致你看到的行为 - 因为在printf()示例中,在调用函数时发生崩溃,而在调用库时在运算符&lt;&lt;(lt)(。)情况下发生崩溃。
答案 1 :(得分:4)
两个递归函数永远不会停止。 似乎在第二种情况下,编译器没有进行尾部优化,因此堆栈溢出。
答案 2 :(得分:2)
这两个函数都会在我的机器上触发堆栈溢出。我正在用MS Visual Studio 2005编译它。也许你应该指定你的平台&amp;编译器,这将有助于调查......
也许你在调试模式下编译了一些东西,你的“cout”实现包括一些由于堆栈损坏而无法执行的检查?也许您的编译器生成了代码,尝试从堆栈溢出中恢复并弹出无效的返回地址?也许你是在移动设备上运行它?很难说不知道平台和编译器。
答案 3 :(得分:1)
无限递归调用是堆栈溢出。至于访问冲突......它实际上取决于STL流的实现。您需要查看流的源代码才能找到...
答案 4 :(得分:0)
虽然大多数人误解了你的问题但答案就在那里。
第二个示例以堆栈溢出结束,因为每个函数调用都将帧推入堆栈。最终,它变得太大了。我同意CătălinPitiş的说法,很难知道为什么在不查看源代码的情况下,流示例会以访问冲突结束。
答案 5 :(得分:0)
这让我想起了堆栈被破坏以及调试器无法捕获失败程序崩溃的问题