【问题标题】:Does that access to a hypothetical array element definitely UB?对假设数组元素的访问肯定是 UB 吗?
【发布时间】:2021-07-27 02:40:17
【问题描述】:
int arr[2];
int b = arr[2];

根据[expr.sub#1]

表达式 E1[E2] 与 *((E1)+(E2)) 相同(根据定义),除了在数组操作数的情况下,如果该操作数是左值且否则为 xvalue。

其中(E1)+(E2) 由 [expr.add#4.2] 确定

当一个整数类型的表达式 J 被添加到一个指针类型的表达式 P 中或从一个表达式 P 中减去时,结果具有 P 的类型。

  • [...]
  • 否则,如果 P 指向具有 n 个元素 ([dcl.array]) 的数组对象 x 的数组元素 i,则表达式 P + J 和 J + P(其中 J 的值为 j)指向 (如果 0 ≤ i + j ≤ n,则可能是假设的)数组元素 i + j,并且表达式 P - J 指向如果 0 ≤ i - j ≤ n 时 x 的(可能假设的)数组元素 i - j李>
  • 否则,行为未定义。

在本例中,arr + 2 指向假设的数组元素,此处没有 UB。 *(arr+2) 的结果是一个泛泛值,它引用了arr + 2 指向的对象。在当前标准中,没有明确规定指针的间接寻址会导致 UB 以及 [basic.life] 的规范性规则。因此,我认为这个例子有两种解释。一个是在该存储中创建了一个 int 类型的实际对象,在这种情况下,int b = arr[2]; 是明确定义的。另一种解释是,根据 [basic.lval#11]

,该存储中没有对象或其他类型的对象

如果程序尝试通过类型与以下类型之一不相似的泛左值访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 对应于对象动态类型的有符号或无符号类型,或
  • [...]

在这种情况下,例子被判断为UB。所以,我想知道这个例子肯定是UB还是和我分析的一样(即未指明的情况)?

【问题讨论】:

  • “明确表示指针的间接寻址会导致 UB” - 并非每个表现出未定义行为的程序都必须明确给出未定义行为。相反,每个行为未明确定义的程序都表现出未定义的行为。在这种情况下,您有 UB,因为数组下标的规则专门适用于数组范围内的索引。
  • @user2407038 [expr.sub] 没有指定数组下标值的限制。
  • 您询问的是*(arr + 2),但引用了标准的一部分,它给出了arr + 2 的行为。此外,它确实限制了下标的值(“0 ≤ i + j ≤ n”),但在这种情况下,无论如何都满足条件。条件是这样的,因为它想要包含一个过去的指针,但不要将 expr.sub 误认为任何规则,即在 arr + 2 存在一个对象。
  • 你的程序有UB的原因大致是:它要求一个对象存在于arr + 2。标准中没有规则说那里存在对象。特别是,decl.array 表示““NU 数组”类型的对象由连续分配的 N 类型 U 子对象的非空集合组成,称为数组元素,编号为 0 到 N-1”。
  • 从语言律师的角度来看,这确实是一个棘手的问题,因为您必须付出相当大的努力才能确定arr + 2 不引用任何上述数组子对象的事实(并且有很多注释可以非常清楚地说明这一事实,但是,根据您的问题,您似乎只是在寻找基于规范规则的推理)

标签: c++ language-lawyer


【解决方案1】:

在本例中,arr + 2 指向假设的数组元素,并且 这里没有UB。 *(arr+2) 的结果是一个泛泛值,它指 到arr + 2指向的对象。

您在这里跳过了一个步骤。 hypothetical 数组元素是假设的。这意味着它实际上并不存在。但是在下一句中,您正在谈论“arr + 2 指向的对象”。该地址没有对象。

您从 [basic.lval#11] 中引用的内容是关于别名的 - 对象确实存在。它不适用于假设对象。

为了澄清您的一个 cmets 的误解,应该注意的是,在 C++ 中,内存可能存在而没有对象存在于该地址。放置 new 的全部目的是在内存中创建一个以前不存在的对象。所以你不能假设arr+2一定有某种对象。

【讨论】:

  • 换句话说,如果我们没有在存储中显式创建一个对象(包括一些隐式创建这样一个对象的操作),我们不能假设一个对象存在于那个存储中,即使有一个对象实际上存在于机器上的那个存储中,对吧?
  • @xmh0511:不,这没有意义。您正在混合 C++ 抽象模型(其中有对象)和机器的底层物理现实(其中有 CPU 和物理 RAM)。现在这两者已经合理地对齐了,但是编译器肯定会从 C++ 方面查看你的 C++ 代码。 *(arr+2) 是一个 C++ 表达式。 C++ 编译器可以合理地确定该值在寄存器中,因为它不引用内存中的 C++ 对象。或者,它可以决定整段代码必须无法访问,并在代码生成时将其丢弃。
  • 考虑这个例子,int arr[2][2]; int(*arr_ptr)[2] = arr; int b = (*arr_ptr)[2],是(*arr_ptr)[2] UB吗? *arr_ptr 指的是array of 2 int 类型的数组对象,在本例中,该元素是(*arr_ptr) 指的数组的假设元素。
  • @xmh0511:这是一个不同的问题。
  • @xmh0511 (*arr_ptr)[2] 在您的示例中是 UB。 arr[1][0] 确实存在于该地址,但(*arr_ptr) + 2 没有指向它。 指向 是一个特殊的标准术语,在[basic.compound]/3 中定义。另请参阅,例如,this answer 和相关的 cmets。
猜你喜欢
  • 2017-12-12
  • 2013-01-22
  • 2022-11-15
  • 2014-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-04
  • 2019-04-30
相关资源
最近更新 更多