12

我经常看到添加一个值的代码,例如一个指针的长度,然后使用这个值,例如

T* end = buffer + bufferLen;//T* + size_t

if (p < end)

但是,缓冲区是否可能已分配到足够接近“buffer + bufferLen”可能溢出的内存末尾(例如 0xFFFFFFF0 + 0x10),即使 p 是有效的元素地址,也会导致“p < end”为假(例如 0xFFFFFFF8)。

如果可能的话,当我看到许多与开始/结束范围一起工作的事情时,如何避免在最后一个元素之后结束下一个元素

4

4 回答 4

9

从标准:

5.9 关系运算符 [expr.rel]

如果两个指针指向同一个数组的元素或一个超出数组末尾的元素,则指向具有较高下标的对象的指针比较高。

所以你不必担心;一致的实现将确保过去指针与数组的其余部分正确比较。此外,

3.7.4.1 分配函数[basic.stc.dynamic.allocation]

[...] 返回的指针应适当对齐,以便可以将其转换为具有基本对齐要求 (3.11) 的任何完整对象类型的指针,然后用于访问分配的存储中的对象或数组 [.. .]

这意味着返回的指针应该能够被视为指向适当大小数组开头的指针,因此 5.9 继续成立。如果分配函数调用是调用operator new[](5.3.4:5) 的结果,就会出现这种情况。

实际上,如果您在一个平台上,可以想象分配器(不合规地)返回一个以 结尾的内存块0xFFFFFFFF,那么在大多数情况下,您可以编写

if (p != end)
于 2012-08-20T13:03:16.297 回答
1

连续内存分配的元素不可能有不连续的地址。end总是有一个比 值更高的地址start

例如,如果分配恰好在 0xFFFFFFFF 结束,意思end是 0x00000000,这将是一个错误,应该修复代码以适应这种情况。

尽管在某些平台上,这种情况在设计上是不可能的,但为了简单起见,它可能是逻辑上的合理妥协。例如,我会毫不犹豫地编写if(p < end)Windows 用户模式应用程序。

于 2012-08-20T12:57:21.067 回答
1

没错,在许多[start, end)配对算法中,端点都超过了最后一个有效条目。但是您的实现不应该取消引用 end,实际访问的最后一个条目应该是end-1,保证在有效区域中。如果您的算法取消引用*end,那么就是一个错误。事实上,有一些测试分配器有意将区域放置在有效页面的最后一个字节上,紧接着是一个未分配的区域。使用这样的分配器,取消引用的算法*end将导致保护错误。

FLG_HEAP_PAGE_ALLOCS

打开页堆调试,它会验证动态堆内存操作,包括分配和释放,并在检测到堆错误时导致调试器中断。

此选项在为图像文件设置时启用整页堆调试,在系统注册表或内核模式中设置时启用标准页堆调试。

  • 全页堆调试(用于 /i)在分配结束时放置一个不可访问的页。

  • 标准页堆调试(用于 /r 或 /k)在分配被释放时检查分配。

为图像文件设置此标志与在命令行中为图像文件键入 gflags /p enable /full 相同

至于指针溢出的问题:没有操作系统分配包含 VA 地址 0xFFFFFFFF 的页面,同样没有操作系统分配包含 0x00000000 的页面。为了发生这种溢出, 的大小*start必须足够大,以便start+1在有效范围结束时跳过所有保留的 VA。但在这种情况下,分配给的地址start应该至少比最后一个有效的 VA 地址低一个这样的大小这意味着start+1将是有效的(start+N只要start分配为 ,它也总是有效的sizeof(*start)*N)。

于 2012-08-20T13:02:20.637 回答
-1

别担心。您的分配器(可能是new,但也可能是其他东西)不会给您提供如此接近内存结尾的东西,以至于它环绕。

而是担心边界检查。你永远不会得到一个像这样环绕的分配,所以只要你不溢出数组(无论如何都有未定义的行为),你最终不会环绕。

还需要注意的是,为内核保留了大块的进程地址空间。在大多数操作系统上,这个高阶区域是保留的。

于 2012-08-20T12:57:07.177 回答