我正在研究给定进程的内存布局。我注意到每个进程的起始内存位置不是0。在这个网站上,TEXT 从0x08048000开始。一个原因可能是用 NULL 指针来区分地址。我只是想知道是否还有其他好的理由?谢谢。
3 回答
空指针实际上不必为 0。在 C 标准中保证,当在指针的上下文中给出 0 值时NULL
,编译器会将其视为。
但是您在源代码中使用的 0 只是语法糖,与空指针值“指向”的实际物理地址无关。
有关详细信息,请参阅:
操作系统上的应用程序有其独特的地址空间,它将其视为连续的内存块(内存在物理上不是连续的,它只是操作系统给每个程序的“印象”)。
在大多数情况下,每个进程的虚拟内存空间以相似且可预测的方式布局(这是 Linux 进程中的内存布局,32 位模式):
(图片来自内存中程序的剖析)
查看文本段(基于 x86 的默认 .text 为 0x08048000,由默认链接描述文件选择用于静态绑定)。
为什么是神奇的 0x08048000?可能是因为 Linux 从 System V i386 ABI 借用了该地址。
... 为什么 System V 使用 0x08048000?
选择该值以容纳 .text 部分下方的堆栈,向下增长。0x48000 字节可以由 .text 部分已经需要的相同页表映射(因此在大多数情况下保存页表),而剩余的 0x08000000 将为需要堆栈的应用程序留出更多空间。
0x08048000以下有什么吗?可能什么都没有(只有 128M),但您几乎可以使用 mmap() 系统调用映射任何您想要的东西。
也可以看看:
我想总结一下:
每个进程都有自己的一组页表,但有一个问题。启用虚拟地址后,它们将应用于机器中运行的所有软件,包括内核本身。因此,必须为内核保留一部分虚拟地址空间。
因此,虽然进程获得了自己的地址空间。如果不向内核分配块,它将无法寻址内核代码和数据。
这始终是它出现的第一个内存块,因此包括地址 0。用户模式空间开始于此之外,因此堆栈和堆都驻留于此。
NULL
与指针区别
即使用户模式空间从地址开始0
,也不会有任何数据分配给该地址0
,因为这些数据将在堆栈或堆中,它们本身并不从用户区域的开头开始。因此NULL
(具有 的值0
)仍然可以使用,而不是这种布局的原因。
然而,与第一个块是内核内存相关的一个好处NULL
是任何读取/写入 NULL 的尝试都会引发分段错误。
加载程序将二进制文件分段加载到内存中:文本(常量)、数据、代码。没有必要从 0 开始,并且由于 C 存在访问 null 周围的错误的问题,这样a[i]
甚至是危险的。这允许(在某些处理器上)拦截分段错误。
这将是 C 运行时从 0 引入线性地址空间。这可能是可以想象的,其中 C 是操作系统的实现语言。但毫无用处;让堆从 0 开始。内存模型是段之一。代码段可能受到某些处理器的保护,不会被修改。
段分配发生在 C 运行时管理的内存块中。
我可能会补充一点,操作系统本身经常使用物理 0 及更高的值。