受到这个问题的启发。
显然在以下代码中:
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
if( GetTickCount() > 1 ) {
char buffer[500 * 1024];
SecureZeroMemory( buffer, sizeof( buffer ) );
} else {
char buffer[700 * 1024];
SecureZeroMemory( buffer, sizeof( buffer ) );
}
return 0;
}
使用 Visual C++ 10 使用默认堆栈大小(1 MB)进行编译,并对 (/O2) 进行优化,会发生堆栈溢出,因为程序尝试在堆栈上分配 1200 KB。
上面的代码当然有点夸张地显示了问题——以一种相当愚蠢的方式使用了大量的堆栈。然而,在实际场景中,堆栈大小可能更小(例如 256 KB),并且可能有更多具有较小对象的分支,这将导致总分配大小足以溢出堆栈。
这毫无意义。最坏的情况是 700 KB - 它将是构建一路上总大小最大的局部变量集的代码路径。在编译期间检测该路径应该不是问题。
因此编译器生成的程序尝试分配比最坏情况更多的内存。根据这个答案 LLVM 也做了同样的事情。
这可能是编译器的缺陷,或者可能有一些真正的原因这样做。我的意思是,也许我只是不明白编译器设计中的某些内容可以解释为什么需要以这种方式进行分配。
为什么编译器希望程序在最坏的情况下分配比代码需要更多的内存?
我只能推测编译器设计者认为这种优化太不重要了。或者,也许存在一些微妙的安全原因。
顺便说一句,在 Windows 上,当线程开始执行时,堆栈会被完整保留,但会根据需要进行提交,因此即使您保留了很大的堆栈,您也不会真正花费太多“实际”内存。
在 32 位系统上保留大堆栈可能是一个问题,其中大量线程可能会占用可用地址空间,而无需真正提交太多内存。在 64 位上,你就是黄金。
这可能取决于您对 SecureZeroMemory 的使用。尝试用常规 ZeroMemory 替换它,看看会发生什么 - MSDN 页面本质上表明 SZM 除了其签名含义之外还有一些额外的语义,它们可能是错误的原因。
在 ideone 上使用 GCC 4.5.1 编译时,以下代码将两个数组放置在同一地址:
#include <iostream>
int main()
{
int x;
std::cin >> x;
if (x % 2 == 0)
{
char buffer[500 * 1024];
std::cout << static_cast<void*>(buffer) << std::endl;
}
if (x % 3 == 0)
{
char buffer[700 * 1024];
std::cout << static_cast<void*>(buffer) << std::endl;
}
}
输入:6
输出:
0xbf8e9b1c
0xbf8e9b1c
如果你想要这种优化,答案可能是“使用另一个编译器”。
操作系统分页和字节对齐可能是一个因素。此外,内务处理可能会使用额外的堆栈以及调用该函数内其他函数所需的空间。