【问题标题】:Is &arr[size] valid?&arr[size] 有效吗?
【发布时间】:2016-02-12 14:36:24
【问题描述】:

假设我有一个函数,调用如下:

void mysort(int *arr, std::size_t size)
{
  std::sort(&arr[0], &arr[size]);
}

int main()
{
  int a[] = { 42, 314 };
  mysort(a, 2);
}

我的问题是:mysort(更具体地说,&arr[size])的代码是否定义了行为?

我知道如果替换为arr + size 将完全有效;指针算法允许正常指向末尾。但是,我的问题是关于 &[] 的使用。

根据 C++11 5.2.1/1,arr[size] 等效于 *(arr + size)

引用5.3.1/1,一元*的规则:

一元* 运算符执行间接:应用它的表达式应该是一个指针 对象类型,或指向函数类型的指针,结果是引用对象的左值或函数 表达式所指向的。如果表达式的类型是“指向T 的指针”,则结果的类型是“T”。 [ 注意: 指向不完整类型的指针(cv void 除外)可以被取消引用。这样得到的左值 可以以有限的方式使用(例如,初始化引用);此左值不得转换为 prvalue,见 4.1。 ——尾注 ]

最后,5.3.1/3 给出了& 的规则:

一元& 运算符的结果是指向其操作数的指针。操作数应为左值……如果类型为 表达式为T,结果类型为“指向T”的指针,并且是一个纯右值,它是指定对象的地址 (1.7) 或指向指定函数的指针。

(我的重点和省略号)。

对此我还不能下定决心。我确信在arr[size] 上强制进行左值到右值的转换将是未定义的。但是代码中没有发生这种转换。 arr + size 不指向对象;但是虽然上面的段落讨论了对象,但它们似乎从未明确指出对象实际存在于该位置的必要性(与 4.1/1 中的左值到右值转换不同)。

所以,问题是:mysort,它的调用方式是否有效?

(请注意,我在上面引用了 C++11,但如果在以后的标准/草案中对此进行更明确的处理,我会非常满意)。

【问题讨论】:

  • 不重复,@Angew 知道:相信我。
  • 与可能的重复项相比,谁能告诉我这个问题的新内容是什么?在我看来,从那里复制粘贴答案也确实可以回答这个问题。
  • §6.5.3.2,第 3 段:...类似地,如果操作数是 [] 运算符的结果,则 & 运算符和 [] 所隐含的一元 * 都不会被计算结果就像删除了 & 运算符并将 [] 运算符更改为 + 运算符。否则,结果是指向其操作数指定的对象或函数的指针。似乎回答了这个问题。从提议的副本中直接复制/粘贴。
  • 我的解读是&arr[size] UB,因为它本质上是&(*something),其中*something 是UB。但我正在等待专家确认。但受骗与此无关。
  • @Bathsheba 怎么不是重复的?

标签: c++ arrays language-lawyer undefined-behavior


【解决方案1】:

这是无效的。您在问题中加粗了“结果是指代表达式指向的对象或函数的左值”。这正是问题所在。 array + size 是一个不指向对象的有效指针值。因此,您对*(array + size) 的引用没有指定结果指的是什么,这意味着&*(array + size) 不需要提供与array + size 相同的值。

在 C 中,这被认为是一个缺陷并已修复,因此规范现在在 &*ptr 中说,&* 都不会被评估。 C++ 还没有收到固定的措辞。这是一个非常古老的仍然活跃的 DR 的主题:DR #232。意图是它是有效的,就像在 C 中一样,但标准没有这样说。

【讨论】:

  • “有效”二字需要澄清。表达式的地址或值是完全有效的。但是,访问该位置的项目是未定义的行为。
  • @ThomasMatthews 我回答的重点是,虽然您所说的是预期的,但它不是标准所说的。
  • 我不确定“P(x) 的对象 x”是否可以解释为“如果 P(x) 则 x 否则??” (这基本上是问题的本质),但 DR 的存在强烈表明了这一点,或者至少表明该问题需要解决。 DR 帮我解决了,谢谢。
【解决方案2】:

在普通 C++ 数组的上下文中,是的。形成数组最后一个元素的地址是合法的。然而,读取或写入它所指向的内容是不合法的(毕竟,那里没有实际的元素)。因此,当您执行&arr[size] 时,arr[size] 形成了您可能认为是对过去结束元素的引用,但尚未尝试实际访问该元素。然后& 为您获取该元素的地址。由于没有人试图真正跟随那个指针,所以没有发生任何不好的事情。

这不是偶然的,这使得指向数组的指针表现得像迭代器。因此&a[0]本质上是数组上的.begin(),而&a[size](其中size是数组中元素的数量)本质上是.end()。 (另请参阅std::array,这最终会更加明确)

编辑:呃,我可能不得不撤回这个答案。虽然它可能适用于大多数情况,但如果存储在数组中的类型具有覆盖的operator&,那么当您执行&a[size] 时,operator& 方法可能会尝试访问a[size] 类型实例的成员没有实例的地方

【讨论】:

    【解决方案3】:

    假设size 是实际的数组大小,您将指向过去元素的指针传递给std::sort()

    所以,据我了解,问题归结为:这个指针是否等同于arr.end()

    毫无疑问,这对于每个现有的编译器都是正确的,因为数组迭代器确实是普通的旧指针,所以&arr[size]arr.end() 的明显选择。

    但是,我怀疑对于普通旧数组迭代器的实际实现是否有特定要求。

    所以,为了论证,你可以想象一个编译器在实际地址之外使用“过去结束”位来在内部实现普通的旧数组迭代器反常地把你的胡子涂成粉红色如果它检测到迭代器和通过指针算法获得的地址之间有任何可能的不一致。 这个怪异的编译器会导致大量现有的 C++ 代码崩溃,而不会真正违反规范,这可能值得设计它的努力......

    【讨论】:

      【解决方案4】:

      如果我们承认arr[i] 只是*(arr + i) 的简写,我们可以将&arr[size] 重写为&*(arr + size)。因此,我们正在取消引用指向过去元素的指针,这会导致未定义的行为。正如您所说的那样,arr + size 将是合法的,因为不会发生取消引用操作。

      巧合的是,这在 Stepanov 的notes(第 11 页)中也以测验的形式出现。

      【讨论】:

        【解决方案5】:

        只要 size 不大于实际数组的大小(以数组元素为单位),它就可以很好地定义。

        因此,如果 main () 调用 mysort (a, 100), &arr [size] 已经是未定义的行为(但很可能未被检测到,但 std::sort 显然也会出错)。

        【讨论】:

        • 但他的观点是 arr[size] 取消引用超出数组边界的值。
        • @TomášZato:不,这不是越界。
        猜你喜欢
        • 1970-01-01
        • 2020-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-06
        • 2020-06-30
        • 1970-01-01
        相关资源
        最近更新 更多