John Dvorak..
5
从技术上讲,操作系统能够在访问时分配任何内存页面,但重要的原因是它不应该或不能:
不同的记忆区域有不同的用途.
码.它可以被读取和执行,但不应该被写入.
文字(字符串,const数组).这个内存是只读的,应该是.
堆.它可以读写,但不能执行.
线程堆栈.两个线程没有理由访问彼此的堆栈,因此操作系统也可以禁止它.此外,当胎面结束时,胎面堆可以被分配.
内存映射文件.对此区域的任何更改都应影响特定文件.如果文件打开以进行读取,则可以在进程之间共享相同的内存页面,因为它是只读的.
内核空间.通常,应用程序不应该(或不能)访问该区域 - 只有内核代码才能访问.它基本上是内核的临时空间,它在进程之间共享.网络缓冲区可以驻留在那里,因此无论数据包何时到达,它都始终可用于写入.
...
操作系统可能会假设所有无法识别的内存访问都是尝试分配更多堆空间,但是:
如果应用程序从用户代码触及内核内存,则必须将其终止.在32位Windows上,上面的所有内存1<<31(最高位设置)或更高3<<30(设置的前两位)是内核内存.您不应该假设用户空间中有任何未分配的内存区域.
如果应用程序想如何使用存储区域,但不告诉操作系统,操作系统可以分配到内存别的东西(OS:肯定的是,你的文件是0x12341234,应用程序,但我想保存我的数据存在).您可以通过触摸数组的末尾来告诉操作系统(无论如何这都是不可靠的),但是更容易调用操作系统功能.这只是一个好主意,该函数调用是"给我堆10MB",而不是"给我堆10MB开始0x12345678"
如果应用程序通过使用它来分配内存,那么它通常根本不会解除分配.这可能会有问题,因为操作系统仍然需要保留未使用的页面(但Java虚拟机也不会解除分配,所以嘿).
程序的不同运行导致变量的不同地址
这被称为内存布局随机化,并且与适当的权限(堆栈空间不可执行)一起使用,以使缓冲区溢出攻击更加困难.你仍然可以杀死应用程序,但不能执行任意代码.
关于内存分配的一些链接(例如在堆中).
你的意思是,分配器使用什么算法?最简单的算法是始终在最快的可用位置进行分配,并从每个内存块链接到下一个内存块,如果它是空闲块或已用块,则存储该标志.更高级的算法总是以2的幂或一些固定大小的倍数来分配块,以防止内存碎片(大量小的空闲块)或链接不同结构的块以更快地找到足够大小的空闲块.
更简单的方法是永远不要取消分配并只指向第一个(也是唯一的)空闲块并保持其大小.如果剩余空间太小,请将其丢弃并向操作系统索取新的空间.
内存分配器没有什么神奇之处.他们所做的只是:*向操作系统询问大区域*并将其分区为较小的区域*,而不会浪费太多空间或*花费太长时间.
一种有趣的算法称为"(二进制)伙伴块".它拥有几个二次幂大小的池,并将它们递归地分成更小的区域.然后,每个区域都可以完全分配,完全免费或分成两个区域(伙伴),这两个区域并非完全免费.如果它被拆分,则一个字节足以保持该块内最大空闲块的大小.