访问(只读)释放的内存是否会导致访问冲突,如果是,在什么情况下?
8 回答
是的,它可以。“访问冲突”(“分段错误”等)是当进程尝试访问(甚至只是为了读取)被操作系统称为“空”、“已释放”或由于其他原因无法访问。这里的关键时刻是操作系统/硬件必须知道内存是空闲的。C 标准库的内存管理功能不一定将“释放”的内存返回给操作系统。他们可能(并且将)保留它以备将来分配。所以在某些情况下,访问“释放”的内存不会导致“访问冲突”,因为从操作系统/硬件的角度来看,这个内存并没有真正被释放。但是,在某些时候,标准库可能会决定将收集的空闲内存返回给操作系统,之后尝试访问该内存通常会导致“访问冲突”。
你问的是“可以”而不是“会”,所以你的答案是肯定的。指向不属于您的程序的内存是未定义的行为,因此任何事情都可能发生。
会吗?依靠。这是非常特定于操作系统的。你也许可以侥幸逃脱,但显然你不能依赖它。尝试取消引用它可能会导致异常,因为操作系统已回收内存以供自己使用。(同样,特定于操作系统)。
在 Windows 上:在 Win32 中管理虚拟内存
可用、保留和提交的虚拟内存
进程中的每个地址都可以被认为是空闲的、保留的或在任何给定时间提交的。一个进程以所有地址空闲开始,这意味着它们可以自由地提交到内存或保留以供将来使用。在使用任何空闲地址之前,必须首先将其分配为保留或提交。 尝试访问保留或空闲的地址会产生 访问冲突异常。
不太可能
内存管理器理论上可以将空间返回给操作系统,但很少这样做。如果不将空间一直返回给内核,MMU 将永远不会参与其中,因此不可能出现故障。
问题是碎片化。可变大小块的分配效率低下,通用分配器无法移动分配的块,因为它们不知道什么指向什么,因此除非它们碰巧相邻,否则它们无法合并块。
测量表明,在稳态进程中,碎片开销往往约为 50%,因此在不可触及的每个其他块中,不可能返回页面,除非它们比块小得多,而且通常不是。
此外,返回嵌入在堆中的页面的簿记挑战是艰巨的,因此大多数内存管理器甚至没有能力,即使在不太可能的情况下他们会有机会。
最后,传统的流程模型不是稀疏对象。这种低级软件开发保守,寿命长。今天开发的分配器可能会尝试稀疏分配,但大多数子系统只是使用 C 库中已经存在的任何内容,这不是随便重写的明智之举。
当然是允许的;C 标准直截了当地说,如果“使用了指向通过调用free
orrealloc
函数释放的空间的指针的值”,则行为未定义。在实践中,由于操作系统的工作方式,你更可能得到垃圾而不是崩溃,但很简单,你不能假设当你调用未定义的行为时会发生什么。
释放的内存不再属于您,确切地说,相应的物理内存页面超出了您的进程地址空间,在您释放后可能已经重新映射到其他进程地址空间,并且您访问的地址尚未分配物理页面和做映射呢;所以“访问冲突”或“段错误”如果访问它,即使是只读也会发生。它通常由处理器硬件(例如 GP#)触发,而不是由操作系统触发。
尽管如果拥有您释放的内存的特定物理页面仍在您的任务上下文控制之下,例如您的进程仍在使用该页面的一部分,则可能不会发生“访问冲突”或“段错误”。
如果您之所以问这个问题是因为您已经分析了代码并发现访问释放的内存会显着提高性能,那么答案很少,如果释放的块很小。如果您想确定,请提供您自己的 malloc() 和 free() 的替代实现。
我们可以访问它,但不鼓励。例如
void main()
{
char *str, *ptr;
str = (char *)malloc(10);
ptr = str;
strcpy(str, "Hello");
printf("%s", str);
free(str);
printf("%s", ptr);
}