【问题标题】:Is it unspecified behavior to compare pointers to different arrays for equality?比较指向不同数组的指针是否相等是未指定的行为吗?
【发布时间】:2011-06-22 00:56:54
【问题描述】:

等式运算符具有关系运算符对指针的语义限制:

==(等于)和 !=(不等于)运算符与关系运算符具有相同的语义限制、转换和结果类型,但它们的优先级较低且结果为真值。 [C++03 §5.10p2]

并且关系运算符对比较指针有限制:

如果两个相同类型的指针p和q指向不同的对象,这些对象不是同一个对象的成员或同一个数组的元素,或者指向不同的函数,或者如果其中只有一个为null,则pq、p=q 未指定。 [§5.9p2]

这是由相等运算符“继承”的语义限制吗?

具体来说,给定:

int a[42];
int b[42];

显然 (a + 3)

【问题讨论】:

  • 有趣的问题。如果是这样,那么所有的自我分配测试呢if (this != &other)
  • 除了不透明的措辞,看起来真的很简单:标准完全规定了在什么情况下两个指针比较不相等。然而,两个不相关对象的哪个指针值(地址)更大,只是(而且 - 恕我直言 - 相当明显)未指定。
  • @Martin:对于具有相同偏移量但针对不同段的近指针的分段架构怎么样?我认为您不希望在这种情况下完全指定相等性,并且标准要求此比较用例格式正确(必须编译、执行等),据我所知。
  • 该标准确实需要有意义的结果,即使在这种情况下 - 即,== 必须只有当它们都是空指针或引用同一个对象时才为真(反之亦然) !=,当然)。
  • 在分段内存中,== 和 != 也必须比较指针的分段部分。

标签: c++ pointers standards unspecified-behavior


【解决方案1】:

只要指针指向相同类型的对象,相等运算符(==!=)的结果就会产生指定的结果。给定两个指向相同类型的指针,以下情况恰好有一个为真:

  1. 两者都是空指针,它们相互比较。
  2. 两者都是指向同一个对象的指针,并且它们相互比较。
  3. 它们是指向不同对象的指针,它们相互比较不相等。
  4. 至少有一个没有初始化,比较的结果也没有定义(事实上,比较本身可能永远不会发生 - 只是试图读取指针进行比较会产生未定义的行为)。李>

在相同的约束下(两个指针都指向相同类型的对象),排序运算符(<<=>>=)的结果仅在它们都是指针时才被指定到同一个对象,或在同一个数组中分离对象(为此,分配有mallocnew 等的内存“块”符合数组的条件)。如果指针指向不属于同一数组的单独对象,则结果未指定。如果一个或两个指针没有被初始化,你有未定义的行为。

尽管标准库中的比较模板(std::lessstd::greaterstd::less_equalstd::greater_equal确实都产生了有意义的结果,即使当/如果内置运算符没有。特别是,它们需要产生总排序。因此,您可以根据需要进行排序,而不是使用内置比较运算符(当然,如果其中一个或两个指针未初始化,则行为仍然未定义)。

【讨论】:

  • 关系运算符已定义,但在给定情况下未指定。没有一个比较运算符(在指针上使用时)是未定义的。我问的是 == 而不是
  • @Fred -- 运算符已定义,但结果未定义。我想我可以尝试重新措辞更清楚一点,但是(IMO)我所说的已经比标准中的措辞更容易理解。
  • 结果未指定,其定义与未定义明显不同(如未定义行为)。也许是术语上的挑剔,但很重要,因为 UB 具有严重的含义;还有一个让我感到惊讶,因为我也认为它们是 UB。
  • 至少在 IMO,说结果未定义与说代码具有未定义行为是不同的,但我做了一些编辑以确保不会产生这种误解。
  • 我认为您使用“结果未定义”来准确表示标准对“未指定”的含义,与“实施定义”相比,它不需要文档,因此不需要一致性。 (我基于未记录的行为缺乏一致性总是包括影响它的非显而易见和未指定的因素。)我们都绝对同意它与未定义不同,但如果你的意思是标准对“未指定”的作用,那么我认为说标准话时最好使用这个词。
【解决方案2】:

由于一致性语义存在混淆,因此这些是 C++ 的规则。 C 使用完全不同的一致性模型。

  1. 未定义的行为是一个矛盾的术语,它意味着翻译器不是你的程序,可以为所欲为。这通常意味着它可以生成代码,这些代码也可以做任何它喜欢的事情(但这是一个推论)。如果标准说行为未定义,则文本实际上对用户没有意义,因为删除该文本不会改变标准对翻译人员的要求。

  2. 格式错误的程序意味着除非另有说明,否则翻译程序的行为是严格定义的:需要拒绝您的程序并发出诊断消息。 这里的主要特殊情况是单一定义规则,如果您违反了程序格式错误但不需要诊断的情况。

  3. 定义的实现要求翻译器包含明确指定行为的文档。在这种特殊情况下,结果可能是未定义的行为,但必须明确说明。

  4. 未指定是一个愚蠢的术语,表示行为来自一个集合。从这个意义上说,良好定义只是一种特殊情况,其中允许的行为集仅包含一个元素。 Unspecified 不需要文档,因此在某种意义上它也意味着与没有文档定义的实现相同。

一般来说,C++ 标准不是语言标准,它是语言标准的模型。要生成实际标准,您必须插入各种参数。其中最容易识别的是实现定义的限制。

标准中有几个愚蠢的冲突,例如,合法的翻译器可以拒绝每个表面上好的 C++ 程序,因为你需要提供 main() 函数,但翻译器只支持 1 个字符的标识符.这个问题可以通过 QOI 或实施质量的概念来解决。它基本上说,谁在乎,没有人会仅仅因为它符合要求而购买该编译器。

从技术上讲,当指针指向不相关的对象时,operator < 的未指定性质可能意味着:您将得到某种结果,它是真或假,但您的程序不会崩溃,但这不是正确的未指定的含义,因此这是一个缺陷:未指定给标准编写者带来了记录允许行为集的负担,因为如果该集是开放的,则它等同于未定义的行为。

其实我提出std::less是为了解决一些数据结构要求key是全序的,而指针不是operator <全序的问题。在大多数使用线性寻址的机器上,less< 相同,但在 x86 处理器上的less 操作可能更昂贵。

【讨论】:

  • 您更愿意用什么术语来描述无法保证表达式将产生的值的情况,但如果代码为可能产生的任何值准备好,代码仍然可以合法地评估表达式?例如,假设 f(x)、f(y) 和 f(z) 在理想情况下应该都产生相同的值,但最多 x、y 或 z 之一可能是损坏的。如果对损坏值的操作产生不确定的结果,即使无法确定 x、y 或 z 是否损坏,也可以(没有副作用)安全地说 temp = f(x); return temp==f(y) ? temp : f(z);
  • 如果不能保证对损坏的值调用 f() 是安全的,那么如果没有保存其他一些方法来判断 x、y 或 z 是否损坏,则无法处理损坏。另一方面,如果调用是安全的,那么即使在存储 temp 时不知道该值是否正确,但仍然可以稍后确定。
  • 拒绝格式错误的程序不需要实现。它必须生成诊断,但它可以继续生成二进制文件或执行其他任何操作。
【解决方案3】:

op==op!= 的语义明确表示映射是除了它们的真值结果。因此,您需要查看为它们的真值结果定义的内容。如果他们说结果是未指定的,那么它是未指定的。如果他们定义了特定的规则,那么它不是。它特别说

两个相同类型的指针比较相等当且仅当它们都为空,都指向同一个函数,或者都代表同一个地址

【讨论】:

  • 我没有将其解读为结果的不确定性被包含在“除了它们的真值结果”中,因为这似乎否定了“具有相同的语义限制”。我不确定这个答案是否是最好的解释方式,但它会解决这个问题。
  • +1 : 我完全同意除了他们的真值结果的解释。虽然它可怕的标准语言:-)
  • 引用的文字已被DR1652取代
猜你喜欢
  • 1970-01-01
  • 2017-04-10
  • 1970-01-01
  • 1970-01-01
  • 2020-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多