问题 C ++最大有效内存地址


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

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

if (p < end)

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

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


9418
2017-08-20 12:54


起源



答案:


从标准:

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)

9
2017-08-20 13:03



有趣;这是否意味着永远不能使用最后一个内存地址(类似于 0)?在x64_86 / x86上,没有人关心,但在嵌入式设备上,一个字节可能“很多”。 - bitmask
第24.2.1.7节中有另一个引用 - “range [i,j]是指数据结构中以i指向的元素开头的元素 但不包括j指向的元素。当且仅当j可从i到达时,范围[i,j]才有效。“ - Flexo♦
虽然这听起来很相关,但这实际上适用于OP所描述的情况吗?看作T不是一个数组。 - Andreas Brinck
@AndreasBrinck如果 buffer 是结果 new[] 然后它指向数组的开头(5.3.4:5)。 - ecatmur
@bitmask我认为你是对的;分配函数无法知道它是否被称为标量对象(可以占用它) 0xff...fff)或数组对象(不能)。 - ecatmur


答案:


从标准:

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)

9
2017-08-20 13:03



有趣;这是否意味着永远不能使用最后一个内存地址(类似于 0)?在x64_86 / x86上,没有人关心,但在嵌入式设备上,一个字节可能“很多”。 - bitmask
第24.2.1.7节中有另一个引用 - “range [i,j]是指数据结构中以i指向的元素开头的元素 但不包括j指向的元素。当且仅当j可从i到达时,范围[i,j]才有效。“ - Flexo♦
虽然这听起来很相关,但这实际上适用于OP所描述的情况吗?看作T不是一个数组。 - Andreas Brinck
@AndreasBrinck如果 buffer 是结果 new[] 然后它指向数组的开头(5.3.4:5)。 - ecatmur
@bitmask我认为你是对的;分配函数无法知道它是否被称为标量对象(可以占用它) 0xff...fff)或数组对象(不能)。 - ecatmur


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

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

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


1
2017-08-20 12:57



关键是end不指向有效元素,而是指向最后一个有效元素之后的元素 - Andreas Brinck


是的,很多 [start, end) 对算法端点 过去 最后一个有效的条目。但是你的实施永远不应该 提领  end,实际访问的最后一个条目应该是 end-1,保证在有效区域。如果您的算法取消引用 *end 然后是一个错误。事实上,有一些测试分配器故意将该区域放在有效页面的最后几个字节上,然后是一个未分配的区域。使用这样的分配器,可以解除引用的算法 *end 会导致保护错误。

FLG_HEAP_PAGE_ALLOCS

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

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

  • 整页堆调试(对于/ i)将一个不可访问的页面放在   分配结束。

  • 标准页堆调试(针对/ r或/ k)将分配检查为   他们被释放了。

为图像文件设置此标志与键入gflags / p相同   在命令行为图像文件启用/完全

至于指针overfllow的问题:没有操作系统分配包含VA地址0xFFFFFFFF的页面,同样没有操作系统分配包含0x00000000的页面。对于这种溢出发生的大小 *start 必须足够大 start+1 在有效范围的末尾跳过所有保留的VA。但在这种情况下,分配的地址为 start 应至少有一个这样的尺寸 下面 最后一个有效的VA地址,这意味着 start+1 将是有效的(它遵循 start+N 也一直有效 start 被分配为 sizeof(*start)*N)。


1
2017-08-20 13:02



没有提到解除引用 end,只将它与其他有效的指针进行比较。 - Luchian Grigore
@LuchianGrigore:我知道,但我在OP之间读到了 - Remus Rusanu
它通常是写的 [start,end) 显示范围的包容性/排他性。 (start,end) 好像 start 也被排除在外。 - Flexo♦
@Flexo:我同意并修复它 - Remus Rusanu


别担心。你的分配器(可能是 new,但也许别的东西)不会给你一些如此接近它所包含的内存末尾的东西。

担心边界检查。你永远不会得到像这样包装的分配,所以只要你没有溢出数组(无论如何都有未定义的行为),你就不会结束。

注意为内核保留大块进程地址空间也很有用。在大多数操作系统上,保留此高阶区域。


-1
2017-08-20 12:57



我想,我们应该接受你的话呢? - bitmask
@bitmask:看到内存地址0被保留,我会说它逻辑上遵循它不会被分配为包含在低位地址的内存末尾的连续分配的一部分。 - Wug
这不会阻止溢出的整数加法。 - bitmask
我想如果你把非数组视为一个数组,但这是一个完全不同的问题。你不会得到一个包裹的块分配,所以只要你正确地检查边界,这将永远不会发生。 - Wug
@Wug如果数组分配位于地址空间的最边缘,“一个过去”数组将换行为0。 - tenfour