C++ 中的进程内存映射

Process memory mapping in C++

#include <iostream>

int main(int argc, char** argv) {
  int* heap_var = new int[1];
  /*
   * Page size 4KB == 4*1024 == 4096
   */
  heap_var[1025] = 1;
  std::cout << heap_var[1025] << std::endl;
  return 0;
}

// Output: 1

在上面的代码中,我在堆中分配了4个字节的space。现在,由于 OS 将虚拟内存映射到页面中的系统内存(每页 4KB),我的虚拟 mems 堆中的 4KB 块将映射到系统内存。为了进行测试,我决定尝试访问我分配的 page/heap-block 中的其他地址并且它有效,但是我不应该被允许从一开始就访问超过 4096 个字节(这意味着索引 1025 作为一个 int 变量是4字节)。

我很困惑为什么我能够从堆块的开始访问 4*1025 字节(超过已分配页面的大小)而没有出现段错误。

谢谢。

平台分配器可能分配的内存远远超过页面大小,因为它计划将该内存 "bucket" 用于其他分配,或者可能在那里保留一些内部状态,很可能在发布版本中远远不仅仅是一个页面大小的虚拟内存块。您也不知道在该特定页面中分配了内存的位置(您可以通过屏蔽一些位来找出)并且没有提及 platform/arch (我假设 x86_64)没有告诉这个页面甚至是 4kb,它可能是 2MB "huge" 页面或类似的东西。

但是通过访问外部数组边界,您会触发未定义的行为,例如读取时崩溃或写入时数据损坏。

不要使用不属于您的内存。

我还应该提到,这可能与 C++ 无关,因为 new[] 运算符通常只是在核心平台库的幕后调用 malloc/calloc(是 libSystem 在 OSX 或 glibcmusl 或 Linux 上的任何其他内容,甚至是拦截分配器)。您遇到的段错误通常来自堆块周围的保护页面,或者在没有保护页面的情况下只是使用未映射的内存。

注意:请勿在家中尝试此操作:在某些情况下,您可能会故意触发被视为 未定义行为一般,但在那个特定的平台上你可能确切地知道那里有什么(一个很好的例子是在 Linux 上滥用 pthread_t opaque 来获得 tid 而没有额外系统调用的开销,但你必须确保您使用的是正确的 libc、该 libc 的正确构建类型、该 libc 的正确版本、构建它的正确编译器等)。