【问题标题】:Reverse iterators and negative strided iterators in C++, using one before beginning as sentinelC ++中的反向迭代器和负跨步迭代器,在开始之前使用一个作为哨兵
【发布时间】:2026-01-19 00:10:02
【问题描述】:

Another way of looking at C++ reverse iteratorsRaymond Chen 写道:

... C++ 语言的一个怪癖:你可以有一个指针 一个集合的“一个过去的结束”,但你不能有一个 集合的“开头前一个”指针。

我知道这可能意味着“未定义的行为”,这几乎是一个对话终结者。但是我很好奇如果忽略这条规则,在现实系统中可能发生的最坏情况是什么。分段错误?指针算术溢出?不必要的分页?

请记住,指针“之前”(如“end”)也不应该被引用,问题似乎是指针只是试图指向它。

我能想象的唯一可能出现问题的情况是内存位置“0”有效的系统。 但即便如此,如果是这样的话,还有更大的问题,(例如 nullptr 本身也会有问题,我猜想按照惯例,环绕可能仍然有效。)

我不是在质疑reverse_iterator 的实现,它带有一个非一的特殊代码。 我想到了这个问题,因为如果你有一个“跨步”迭代器的通用实现,那么负跨步需要一个特殊的逻辑,并且有成本(在代码和运行时)。

任意步幅,包括负步幅,自然会出现在多维数组中。 我有一个多维数组库,原则上我总是允许负步幅,但现在我意识到,如果我完全允许它们并且我不想暴露未定义的行为,它们应该有一个特殊的代码(不同于正例) .

【问题讨论】:

  • “可能发生的最坏情况” 优化器感到困惑并做着奇怪的事情。或者遇到奇怪的崩溃,不得不怀疑它是否是由这个 UB 引起的。
  • The LLVM Project Blog - What Every C Programmer Should Know About Undefined Behavior #1/3 即编译器可以对你做什么。这不仅仅是段错误;当编译器确定一段代码包含 UB 时,它可以删除/更改/“变得奇怪”。
  • 还有Old New Thing - Undefined behavior can result in time travel (among other things, but time travel is the funkiest) 即担心运行时错误但也担心编译器会将您的代码更改为您不希望的内容。
  • 阿丽亚娜号可能会爆炸,或者火星任务失败,人员伤亡很多。这意味着,软件不再被认为是安全的。
  • 除了对未定义行为的一般关注之外,我认为在这种情况下在现代机器上不会发生任何不好的事情(使用平面内存模型和 2 的补码算法)。原因是编译器(通常)不能证明指针指向分配的开始。它可能指向中间。在这种情况下,ptr[-1] 是有效的。因此,编译器失控并没有太大意义。

标签: c++ reverse-iterator


【解决方案1】:

但我很好奇现实中可能发生的最坏情况是什么 如果有人忽略此规则,则系统。分段错误?溢出的 指针算术?不必要的分页?

浪费空间。

为了能够形成“前一个”地址,您需要有可用空间。例如,如果你有一个 1k 的结构,它必须从内存开始至少 1k 开始。在内存有限的小型设备或具有分段内存的 x86 设备上,这可能会很棘手。

要形成一个“过去”的指针/迭代器,您只需要在末尾多出一个字节。因为它无论如何都不能被取消引用,所以没有必要为整个对象留出空间,只需要形成地址的可能性。


来自cmets:

在定义规则时,我们拥有带有专用地址寄存器的真实 CPU,这些寄存器在寄存器加载时根据段大小验证地址。这确保了一个 -1 地址会陷阱......

https://en.wikipedia.org/wiki/Motorola_68000#Architecture

这排除了将“前一个”环绕到段末尾的情况。因为如果段很小,结果可能不存在。

【讨论】:

  • 我猜是问题所在,地址甚至不需要存在,因为它从未被引用过。它可能是 2^64-1,这在大型计算机中也不存在。
  • 但我明白你的意思。可以想象一个物理寻址系统,其中一个物理对象即使没有被读取也必须位于某个位置。并且这不受某些特殊保护(例如模算术)的保护。实际上,这可能是该规则的起源,在物理磁带中,说结尾是开头之前的一个或该母题的任何其他点并不是一个好的解决方案。磁带会跳来跳去只是为了表示这一点。
  • @alfC - 在定义规则时,我们拥有带有专用地址寄存器的真实 CPU,用于在寄存器加载时根据段大小验证地址。这确保了 -1 地址会捕获...en.wikipedia.org/wiki/Motorola_68000#Architecture
  • 这似乎是一个非常有说服力的理由来定义 C 抽象机的方式。 (涵盖这种情况)。我的印象是,对于不关心无效地址的新系统,抽象机必须进行重大更改。我意识到我的问题不仅适用于负步幅,也适用于不划分原始分配范围的步幅,因此,在我的库中,我将警告这两种情况,但说它“可能”是未定义的行为,这在技术上是未定义的行为,但实际上意味着“了解您的目标硬件”。
  • 请将评论作为答案的一部分,以便我接受。