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
你观察到的这个现象,核心和虚拟内存分页机制、堆内存分配器的实际行为这两点有关,咱们一步步拆解:
虚拟内存的整页映射规则:正如注释提到的,操作系统是以固定大小的“页面”(大部分系统是4KB)为单位,把进程的虚拟地址空间映射到物理内存的。哪怕你只通过
new申请了4字节(1个int),底层的内存分配器会向操作系统请求整页的内存——因为操作系统不支持分配小于一页的物理内存映射。这就意味着,你的进程实际上拥有了这一整页(4096字节)的合法虚拟内存权限。堆分配器的额外空间预留:实际的堆分配器(比如glibc的ptmalloc、Windows的HeapAlloc)不会只给你精确的4字节空间。它会在你申请的内存块周围预留一些空间用于管理元数据(比如内存块的大小、是否被占用等),甚至会一次性申请多个连续页面来处理后续的小内存分配请求。你访问的
heap_var[1025]对应的地址偏移是1025 * 4 = 4100字节,仅比单页4096字节多4字节,很大概率还在分配器已经为进程申请好的、被映射的虚拟内存范围内。内存访问错误的触发条件:操作系统只会在你访问未被映射到物理内存的虚拟地址,或者访问的地址没有对应权限(比如写只读页面)时,才会触发段错误(SIGSEGV)或者内存访问异常。在这个例子里,你访问的地址刚好属于进程合法拥有的虚拟内存空间,所以操作系统允许这次读写操作,程序也就正常输出了结果。
⚠️ 重要提醒:这种行为属于C++标准中的未定义行为!这次能正常输出完全是巧合,下次运行可能直接崩溃,或者覆盖其他变量/堆元数据导致难以排查的bug,绝对不要在实际代码中依赖这种行为。
内容的提问来源于stack exchange,提问作者Arnav_Garg

