最近有人向我的一个C程序指出,如果内存块的起始地址足够低,我的一项测试将失败,结果是零附近,导致崩溃。
起初我认为"这是一个令人讨厌的潜在错误",但后来,我想知道:这种情况会发生吗?我从没见过。公平地说,这个程序已经在无数的系统上运行了数百万次,但迄今为止从未发生过。
因此,我的问题是:对malloc()
的调用可能返回的最低内存地址是什么?据我所知,我从未见过像0x00000032这样的地址。
我只对"现代"环境感兴趣,比如Linux、BSD和Windows。此代码不适用于C64,也不适用于任何爱好/研究的操作系统。
首先,既然这是您所要求的,我只考虑现代系统。这意味着它们使用分页内存,并且在0处有一个错误页面来处理空指针取消引用。
现在,据我所知,在任何实际系统上,最小的页面大小都是4k(4096字节)。这意味着您永远不会有低于0x1000的有效地址;任何更低的都将是包含零地址的页面的一部分,因此将排除空指针取消引用故障。
在现实世界中,良好的制度实际上会让你不会走得那么低;现代Linux甚至阻止应用程序有意映射低于可配置默认值(我相信是64k)的页面。其想法是,您希望从空指针(例如p[n]
,其中p
恰好是空指针)到故障的偏移量适中(在Linux的情况下,他们希望内核空间中的代码在尝试访问此类地址时出错,以避免可能导致特权提升漏洞的内核空指针取消引用错误)。
也就是说,在指针指向的数组的边界之外执行指针算术是未定义的行为。即使地址不换行,编译器也可能会做各种事情(要么是为了强化代码,要么只是为了优化),未定义的行为可能会导致程序中断。好的代码应该遵循它所用语言的规则,即不要调用未定义的行为,即使你认为UB是无害的。
您的意思可能是在计算&a - 1
或类似的东西。
请不要这样做,即使指针比较目前在大多数体系结构上实现为无符号比较,并且您知道(uintptr_t)&a
大于当前系统上的一些任意边界。编译器将利用未定义的行为进行优化。他们现在就这样做,如果他们现在不利用它,他们将来也会这样做,不管你可能期望从指令集或平台得到"保证"。
请参阅这则见多识广的轶事了解更多信息。
在一个完全不同的寄存器中,您可能会认为有符号溢出在C中是未定义的,因为过去有不同的硬件选择,如1的补码和符号大小。因此,如果您知道平台是2的补码,那么像(x+1) > x
这样的表达式将检测到MAX_INT
。
这可能是历史原因,但这种推理已不再成立。表达式(x+1) > x
(具有类型为int
的x
)被现代编译器优化为1
,因为有符号溢出是未定义的。编译器作者并不关心不确定性的最初原因曾经是各种可用的体系结构。无论你用指针做什么未定义的事情,都是它们列表中的下一个如果您调用未定义的行为,您的程序明天就会崩溃,这不是因为体系结构发生了变化,而是因为编译器在优化方面越来越积极。
动态分配在heap
上执行。Heap
驻留在进程address space
中,位于text
(程序代码)、initialized data
和uninitialized data
部分之后,请参见此处:http://www.cprogramming.com/tutorial/virtual_memory_and_heaps.html。因此,堆中可能的最小地址取决于这3个段的大小,因此没有绝对的答案,因为它取决于特定的程序。