4

我想知道为什么在以下两个代码片段中 cout 和 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;
}
4

6 回答 6

18

我假设您了解这两个函数在尝试无限递归后由于堆栈耗尽而崩溃。我认为您要问的是:为什么 cout 示例也不会因“堆栈溢出”而崩溃?

我认为答案与编译器对尾递归的检测无关。如果编译器优化了递归,则两个示例都不会崩溃。

我猜想发生了什么。“堆栈溢出”异常在某些情况下(例如,Windows)是通过在堆栈末尾分配的单个虚拟内存“保护页”来实现的。当堆栈访问命中此保护页时,会生成一个特殊的异常类型。

由于 Intel 小粒度页面的长度为 4096 字节,因此保护页面可以保护该大小的内存范围。如果一个函数调用分配了超过 4096 字节的局部变量,那么从它的第一个堆栈访问实际上可能会超出保护页。下一页可以预期是未保留的内存,因此在这种情况下访问冲突是有意义的。

当然,您没有在示例中显式声明任何局部变量。我假设其中一个 operator<<() 方法分配的局部变量不止一页。换句话说,访问冲突发生在 operator<<() 方法或 cout 实现的其他部分(临时对象构造函数等)的开头附近。

此外,即使在您编写的函数中, operator<<() 实现也需要为中间结果创建一些存储。该存储可能由编译器分配为本地存储。不过,我怀疑在你的例子中它会加起来 4k。

真正理解的唯一方法是查看访问冲突的堆栈跟踪以查看触发它的指令。

有访问冲突的堆栈跟踪和错误操作码区域周围的反汇编吗?

如果您使用的是 Microsoft C 编译器,另一种可能性是 printf() 和您自己的函数是用 /Ge 编译的,而 operator<<() 不是,或者只有您的函数是用 /Ge 编译的,并且与所描述的因素相似上面巧合地导致您看到的行为 - 因为在 printf() 示例中,崩溃发生在您的函数被调用时,而在 operator<<() 情况下,您正在调用库。

于 2009-10-15T07:27:21.640 回答
4

两个递归函数都不会停止。似乎在第二种情况下,编译器没有进行尾部优化,因此堆栈溢出。

于 2009-10-15T07:19:44.837 回答
2

这两个函数都会在我的机器上触发堆栈溢出。我正在用 MS Visual Studio 2005 编译它。也许你应该指定你的平台和编译器,这将有助于调查......

也许您在调试模式下编译了一些东西,并且您的“cout”实现包括一些由于堆栈损坏而无法执行的检查?也许您的编译器生成了试图从堆栈溢出中恢复并弹出无效返回地址的代码?也许您正在移动设备上运行它?不知道平台和编译器就很难说。

于 2009-10-15T07:36:01.083 回答
1

无限递归调用是为了堆栈溢出。至于访问冲突......它真的取决于STL流的实现。您需要查看流的源代码以找出...

于 2009-10-15T07:25:00.600 回答
0

尽管大多数人误解了您的问题,但答案就在那里。

第二个示例以堆栈溢出结束,因为每个函数调用都将一个帧推入堆栈。最终,它变得太大了。我同意 Cătălin Pitiş 的观点,即如果不查看源代码,就很难知道为什么流示例以访问冲突结尾。

于 2009-10-15T07:31:27.123 回答
0

这让我想起了堆栈被破坏的问题,调试器没有捕捉到失败的程序崩溃

于 2009-10-20T14:28:06.460 回答