MSVC 中的 Operator new 在调试模式下的行为与在发布模式下的行为不同

jan*_*b04 2 c++ memory memory-management visual-c++ page-fault

new在测试有关页面错误的一些内容时,我发现MSVC 中调试模式和发布模式下的操作方式存在一个奇怪的差异。考虑以下代码1

#include <array>

constexpr size_t PAGE_SIZE = 4096;

int main()
{
    const size_t count = 1000000;
    char* const mem = new char[PAGE_SIZE * count];

    // page align the pointer, c-style casts used for brevity
    auto* pages = (std::array<char, PAGE_SIZE>*)((size_t)mem - (size_t)mem % PAGE_SIZE + PAGE_SIZE);
    
    for (int i = 0; i < count; ++i)
        pages[i][0] = 'a';
}
Run Code Online (Sandbox Code Playgroud)

该代码在大多数体系结构上分配一百万个普通内存。然后它会物理地写入这个分配的内存,因此内存实际上必须“给予” 2给程序 - 而不仅仅是以某种方式为其“保留”。奇怪的是,当这真的发生时。为了调查这个问题,我使用 Visual Studio 调试器单步调试了代​​码,并查看了任务管理器中的内存使用情况图。结果如下:

测量结果

红色时间点是正在启动的程序,绿色时间点/间隔是对 的调用new char[],蓝色时间点/间隔是循环for

事实证明,在调试模式下,new“保留”和“提供”内存给程序。同时,在释放模式下,它仅“保留”它,因为内存是由循环“给出”的。我只期望释放模式中存在的行为 - 我认为仅当发生页面错误时才会将内存“提供”给程序。

为什么会有new这样的行为?这有什么重大影响吗?


1顺便说一句,由于某种原因,更改auto* pagesauto* const pages导致内部编译器错误。

2我对正确的术语有点困惑,所以我用“给定”和“保留”代替。

use*_*301 6

要了解发生了什么,您需要了解两件事:

  1. 调试版本可以为您做很多很酷的事情来帮助您发现错误。一种是将已知值写入程序的内存中,这样您就可以更容易地认识到您已经弄乱了未初始化的存储。
  2. CPU 中的现代内存管理系统很复杂,但它们都倾向于做的一件事就是在必要之前尽可能少做。当程序请求存储时,底层系统会检查是否有足够的虚拟寻址空间,然后几乎总是允许该请求而不填充它。没有找到物理内存并分配给虚拟内存。当访问内存时,将找到并分配物理内存,否则程序将因内存不可用而失败。

第 1 点和第 2 点的组合意味着调试版本new获取内存并立即通过写入未初始化内存检测模式来访问它,并强制系统在绿色区域中查找并移交真实内存。作为一个额外的好处,如果计算机确实耗尽了物理存储空间,程序很可能会在这里崩溃,而不是在未来某个看似随机的点无法满足请求时崩溃。

的发布版本new没有执行第 1 点,因此物理内存获取会按照第 2 点延迟。new快速退出绿色区域,无需任何物理内存。如果所请求的部分或全部内存从未被使用过,则计算机将因不必执行满足请求的工作而受益。该程序确实使用了所请求的存储空间for,因此系统被迫在蓝色区域中查找并提供物理内存。