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