我经常看到代码添加一个值,例如指针的长度,然后使用这个值,例如
T* end = buffer + bufferLen;//T* + size_t
if (p < end)
但是,缓冲区是否有可能在“buffer + bufferLen”可能溢出的内存末尾附近分配(例如0xFFFFFFF0 + 0x10),导致“p <end”为假,即使p是有效的元素地址(例如0xFFFFFFF8)。
如果可能的话,当我看到许多与开始/结束范围一起工作的东西时,如何在最后一个之后结束下一个元素时如何避免它
从标准:
C ++ 11
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)
从标准:
C ++ 11
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)
连续内存分配的元素不可能具有非连续的地址。 end
总是有一个比价值更高的地址 start
。
例如,在分配恰好以0xFFFFFFFF结束的情况下,意思是 end
将是0x00000000,这将是一个错误,应该修复代码以适应该场景。
在某些平台上,虽然这种情况在设计上是不可能的,但为了简单起见,可能是逻辑上的合理折衷。例如,我会毫不犹豫地写 if(p < end)
在Windows用户模式应用程序上。
是的,很多 [start, end)
对算法端点 过去 最后一个有效的条目。但是你的实施永远不应该 提领 end
,实际访问的最后一个条目应该是 end-1
,保证在有效区域。如果您的算法取消引用 *end
然后是一个错误。事实上,有一些测试分配器故意将该区域放在有效页面的最后几个字节上,然后是一个未分配的区域。使用这样的分配器,可以解除引用的算法 *end
会导致保护错误。
FLG_HEAP_PAGE_ALLOCS
打开页堆调试,验证动态堆内存
操作,包括分配和释放,并导致调试器
在检测到堆错误时中断。
此选项在为图像文件设置时启用完整页面堆调试
和在系统注册表或内核中设置时的标准页堆调试
模式。
为图像文件设置此标志与键入gflags / p相同
在命令行为图像文件启用/完全
至于指针overfllow的问题:没有操作系统分配包含VA地址0xFFFFFFFF的页面,同样没有操作系统分配包含0x00000000的页面。对于这种溢出发生的大小 *start
必须足够大 start+1
在有效范围的末尾跳过所有保留的VA。但在这种情况下,分配的地址为 start
应至少有一个这样的尺寸 下面 最后一个有效的VA地址,这意味着 start+1
将是有效的(它遵循 start+N
也一直有效 start
被分配为 sizeof(*start)*N
)。
别担心。你的分配器(可能是 new
,但也许别的东西)不会给你一些如此接近它所包含的内存末尾的东西。
担心边界检查。你永远不会得到像这样包装的分配,所以只要你没有溢出数组(无论如何都有未定义的行为),你就不会结束。
注意为内核保留大块进程地址空间也很有用。在大多数操作系统上,保留此高阶区域。