C ++标准(和C就此而言)允许创建(而不是取消引用)指向超过数组末尾的一个元素的指针。这是否意味着永远不会在最后一个元素在内存边界结束的位置分配数组?我理解在实践中,部分/全部实现可能遵循此约定,但以下哪一项是正确的:
C的情况有什么不同吗?
更新
似乎 1 是正确的答案。请参阅下面的詹姆斯坎泽的答案,并看到efence
(http://linux.die.net/man/3/efence - 感谢Michael Chastain指向它的指针)
答案 0 :(得分:37)
实现必须允许指向结尾的指针 存在。它是如何做到这一点的。在很多机器上,你 可以安全地将任何值放入指针,没有风险(除非 你取消引用它);在这样的系统上,一个接一个的结束 指针可能指向未映射的内存 - 我 实际上在Windows下遇到了一个案例。
在其他机器上,只需加载指向未映射内存的指针 进入寄存器会陷阱,导致程序崩溃。上 这样的机器,实施必须确保这不是 通过拒绝使用最后一个字节或单词来发生 分配内存,或通过确保所有使用指针 除了解除引用之外,它可以避免任何可能的指令 导致硬件将其视为无效指针。 (大多数这样的 系统具有单独的地址和数据寄存器,并且只会 如果指针被加载到地址寄存器中则陷阱。如果 数据寄存器足够大,编译器可以安全加载 指针指向例如数据寄存器比较。这是 通常是必要的,因为地址寄存器并不总是如此 支持比较。)
回答你的最后一个问题:C和C ++在这方面完全相同; C ++只是接管了C的规则。
答案 1 :(得分:6)
§3.9.2/ 3 [化合物类型]有一个有趣的段落:
指向void的指针类型或指向对象类型的指针称为对象指针类型。 [...]对象指针类型的有效值表示内存中的字节地址(1.7)或空指针(4.10)。
与§5.7/ 5 [添加剂操作符]中的文本一起:
[...]此外,如果表达式P指向数组对象的最后一个元素, 表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向 一个超过数组对象的最后一个元素,表达式(Q)-1指向数组的最后一个元素 对象
如果要求一个接一个的指针必须有效,那么似乎无法分配以内存中最后一个字节结尾的数组。如果允许一个接一个的指针无效,我不知道答案。
§3.7.4.2/ 4 [解除分配函数]部分指出:
使用无效指针值的效果 (包括将其传递给释放函数)未定义。
因此,如果必须支持比较分配数组的一个结束指针,那么一个接一个结束的指针必须是有效的。
基于我得到的评论,我假设一个实现可以分配一个数组,而不必关心数组的一个接一个指针是否可用。但是,我想在标准中找到相关的段落。
答案 2 :(得分:4)
你是对的。假设一个假设的实现使用线性寻址的内存和表示为16位无符号整数的指针。还假设空指针表示为零。最后,假设您要求使用char *p = malloc(16);
的16字节内存。然后它保证你会得到一个数字值小于65520的指针。值65520本身不会有效,因为你正确地指出,假设分配成功,{{1是一个有效的指针,不能是空指针。
但是,现在假设一个假设的实现使用线性寻址的内存和表示为32位无符号整数的指针,但只有16位的地址空间。再次假设空指针表示为零。最后,再次假设你要求16字节的内存,p + 16
。然后它只能保证你会得到一个数值小于或等于65520的指针。值65520本身是有效的,只要实现确保向它添加16就可以得到值65536,减去16会使您返回65520.即使地址65536处根本不存在任何内存(物理或虚拟),这也是有效的。
答案 3 :(得分:3)
标准明确说明了将指针递增到最后一个元素时会发生什么。它为您提供了一个值,该值只能用作比较,以检查您是否在阵列末尾或之前。指针很可能指向某个其他对象的有效分配内存,但这是完整的未定义(实现定义?)行为,并且使用该指针本身肯定是未定义的行为。
我得到的是,一个接一个的结束指针就是这样:当你将指针递增到最后一个元素时,它是你得到的指针,在一个非常标记数组的末尾便宜的方式。但请注意,比较不相关对象的指针是完全没有意义的(如果我没有弄错,甚至是未定义的行为)。因此,跨越不同对象的指针“值”可能存在重叠的事实是没有问题的,因为在利用这一点时,您将进入未定义行为的土地。
答案 4 :(得分:-2)
这取决于实施。至少在Visual C ++中,不使用任何数组绑定检查,您可以创建一个指针,超过数组末尾的任意数量的元素。如果取消引用它,只要您访问的内存地址位于程序的已分配堆/堆栈中,它仍然可以工作。您将读取/修改该内存位置中的任何值。如果地址超出分配的内存空间,则会出错。
调试器有检查来检测这些,因为这种编码会产生很难跟踪的错误。