【问题标题】:UB When Dereferencing Array of UnionsUB 取消引用联合数组时
【发布时间】:2019-04-23 16:00:25
【问题描述】:

其中哪些是未定义的行为:

template <class T> struct Struct { T t; };

template <class T> union Union { T t; };

template <class T> void function() {
  Struct aS[10];
  Union aU[10];

  // do something with aS[9].t and aU[9].t including initialization

  T *aSP = reinterpret_cast<T *>(aS);
  T *aUP = reinterpret_cast<T *>(aU);

  // so here is this undefined behaviour?
  T valueS = aSP[9];
  // use valueS in whatever way

  // so here is this undefined behaviour?
  T valueU = aUP[9];
  // use valueU in whatever way

  // now is accessing aS[9].t or aU[9].t now UB?
}

是的,最后 3 个操作中哪个是 UB?

(我的推理:我不知道结构,如果有任何要求它的大小与其单个元素相同,但 AFAIK 联合必须与元素的大小相同。我不知道联合的对齐要求,但我猜是一样的。对于结构我不知道。在联合的情况下,我猜它不是UB,但正如我所说,我是真的真的不确定。对于结构我真的不知道)

【问题讨论】:

  • 认为哪个是 UB,为什么,为什么你不确定?
  • 不要让我们为您做功课,而是告诉我们的想法和原因,并要求我们更正或确认一些具体的推理。
  • 另见stackoverflow.com/a/25377970/560648en.cppreference.com/w/cpp/types/is_standard_layoutT 是什么?您没有构造任何Ts,但取决于T 是什么,这可能无关紧要......但您必须提供所有必要的信息和上下文。
  • 如给定的,最后两个操作都是UB。

标签: c++ undefined-behavior


【解决方案1】:

tl;dr:上面代码中的最后两个语句将始终调用未定义的行为,只需将指向联合的指针转换为指向其成员类型之一的指针通常就可以了,因为它实际上并没有做任何事情(它是最坏的情况是未指定,但绝不是未定义的行为;注意:我们只讨论转换本身,使用转换的结果访问对象是完全不同的故事)。


根据 T 的最终结果,Struct&lt;T&gt; 可能是标准布局结构 [class.prop]/3 在这种情况下

T *aSP = reinterpret_cast<T *>(aS);

会被很好地定义,因为Struct&lt;T&gt; 可以与其第一个成员(类型为T[basic.compound]/4.3 指针互转换。以上reinterpret_cast等价于[expr.reinterpret.cast]/7

T *aSP = static_cast<T *>(static_cast<void *>(aS));

这将调用数组到指针的转换[conv.array],导致Struct&lt;T&gt;* 指向aS 的第一个元素。然后这个指针被转换为void*(通过[expr.static.cast]/4[conv.ptr]/2),然后再转换为T*,通过[expr.static.cast]/13是合法的:

“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2 T 的指针”类型的纯右值,其中T是一个对象类型,并且 cv2 的 cv 限定与 cv1 相同或更高。如果原始指针值表示内存中某个字节的地址A,并且A不满足T的对齐要求,则结果指针值未指定。 否则,如果原始指针值指向一个对象a,并且有一个T类型的对象b(忽略cv-qualification)与a指针可互转换,则结果为指向b 的指针。否则,指针值不会因转换而改变。

同样,

T *aUP = reinterpret_cast<T *>(aU);

如果Union&lt;T&gt; 是标准布局联合,并且在基于当前标准草案的即将到来的 C++ 版本(其中联合和一它的成员总是指针可相互转换的[basic.compound]/4.2

但是,以上所有内容都无关紧要,因为

T valueS = aSP[9];

T valueU = aUP[9];

无论如何都会调用未定义的行为。 aSP[9]aUP[9](根据定义)分别与 *(aSP + 9)*(aUP + 9) 相同 [expr.sub]/1。这些表达式中的指针运算以[expr.add]/4

为准

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

  • 如果 P 的计算结果为空指针值,而J 的计算结果为 0,则结​​果为空指针值。
  • 否则,如果P 指向具有n 个元素的数组对象x 的元素x[i],则表达式P + JJ + P(其中J 具有值j) 指向(可能是假设的)元素 x[i+j] 如果 0≤i+j≤n 并且表达式 P - J 指向(可能是假设的)元素x[i−j] if 0≤i−j≤n.
  • 否则,行为未定义。

aSPaUP 不指向数组的元素。即使aSPaUP 可以与T 指针互转换,您也只能访问元素0 并计算假设的单元素数组的元素1 的地址(但不能访问)...

【讨论】:

  • 我像你一样不懂[basic.compound]/4.2。对我来说,这里有一个指向联合的指针和一个指向与联合元素相同类型的指针。不是“一个是联合对象,另一个是该联合的非静态数据成员”。虽然可能是错误的
  • @MartinMorterol 我不确定你的意思。 (标准布局)联合及其元素之一是指针可互转换的,仅意味着(通过上面引用的 [expr.static.cast]/13)将指向类型的指针转​​换为“指向类型的指针”的结果of the other" 将是指向另一个对象的有效指针值……
  • 好的,谢谢我明白了。但我不明白你为什么说“aUP 不指向数组的元素”来自 [conv.array]“结果是指向数组第一个元素的指针。”。那么,aUP 应该指向数组的一个元素?
  • @MartinMorterol aSaU 上的数组到指针转换的结果指向一个对象,该对象是数组的第一个元素。但是reinterpret_cast 的结果将(在最好的情况下)指向一个对象,该对象是第一个对象的子对象(成员),并且该子对象不是数组的元素……
  • 那么T valueS = aSP[0] 是未定义的行为是真的吗?
【解决方案2】:

所以如果我们查看reinterpret_cast (here) 的文档

5) 任何对象指针类型 T1* 都可以转换为另一个对象 指针类型 cv T2*。这完全等同于 static_cast(static_cast(expression)) (这意味着如果 T2 的 对齐要求不严格于 T1 的,的值 指针不改变并转换得到的指针返回 到它的原始类型产生原始值)。在任何情况下, 只有在允许的情况下,才能安全地取消引用结果指针 类型别名规则(见下文)

现在说什么别名规则?

每当尝试读取或修改存储的值时 DynamicType 类型的对象通过 AliasedType 类型的左值, 除非满足以下条件之一,否则行为未定义:

  1. AliasedType 和 DynamicType 相似。
  2. AliasedType 是 DynamicType 的(可能是 cv 限定的)有符号或无符号变体。
  3. AliasedType 是 std::byte、(C++17 起)char 或 unsigned char:这允许将任何对象的对象表示检查为 一个字节数组。

所以不是 2 也不是 3。可能是 1?

类似的:

非正式地,两个类型是相似的 if,忽略顶层 简历资格:

  1. 它们是同一类型;或
  2. 它们都是指针,指向的类型相似;或
  3. 它们都是指向同一个类的成员的指针,所指向的成员的类型相似;或
  4. 它们都是相同大小的数组或都是未知边界的数组,并且数组元素类型相似。

还有,from C++17 draft

如果满足以下条件,两个对象 a 和 b 是指针可互转换的:

  • 它们是同一个对象,或者
  • 一个是联合对象,另一个是该对象的非静态数据成员 ([class.union]),或者
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有 非静态数据成员,该对象的任何基类子对象 ([class.mem]),或
  • 存在一个对象 c,使得 a 和 c 可以指针互转换,而 c 和 b 可以指针互转换。

如果两个对象是指针可互转换的,那么它们具有相同的 地址,并且可以从指针中获得指向一个的指针 通过 reinterpret_cast 到另一个。 [ 注意:数组对象及其 第一个元素不是指针可相互转换的,即使它们有 同一个地址。 —— 尾注]

所以,对我来说:

T *aSP = reinterpret_cast<T *>(aS); // Is OK
T *aUP = reinterpret_cast<T *>(aU); // Is OK. 

【讨论】:

    【解决方案3】:

    我找到了c++ - Is sizeof(T) == sizeof(int)。这指定结构不必具有与其元素相同的大小 (sigh)。至于工会,可能同样适用(阅读答案后,我被引导相信如此)。仅此一项就足以使这种情况成为 UB。但是,如果sizeof(Struct) == sizeof(T)https://stackoverflow.com/a/21515546 中的“已确定”,则指向 aSP[9] 的指针将与 aS[9] 的位置相同(至少我认为是这样),并且 reinterpret_cast'这是由标准保证的(根据https://stackoverflow.com/a/21509729中的引用)。

    编辑:这实际上是错误的。正确答案是here

    【讨论】:

      猜你喜欢
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 2013-07-06
      • 1970-01-01
      • 2021-02-08
      • 2015-06-13
      • 2014-01-11
      • 1970-01-01
      相关资源
      最近更新 更多