UB解引用联合数组时

时间:2019-04-23 16:00:26

标签: c++ undefined-behavior

其中哪些是不确定的行为:

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,但正如我所说,我是真的真的不确定。对于结构,我实际上不知道)

3 个答案:

答案 0 :(得分:8)

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


取决于T最终是什么,在这种情况下,Struct<T>可能是标准布局结构[class.prop]/3

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

将得到很好的定义,因为Struct<T>可以与它的第一个成员(类型为T[basic.compound]/4.3进行指针互换。在reinterpret_cast以上相当于[expr.reinterpret.cast]/7

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

将调用数组到指针的转换[conv.array],从而导致Struct<T>*指向aS的第一个元素。然后将此指针转换为void*(通过[expr.static.cast]/4[conv.ptr]/2),然后转换为T*,通过[expr.static.cast]/13合法:

  

可以将“指向 cv1 void的指针的prvalue转换为”指向 cv2 T的指针的prvalue,其中T是对象类型,而 cv2 是与 cv1 相同的cv限定,或具有更大的cv限定。如果原始指针值表示存储器中字节的地址A,并且A不满足T的对齐要求,则未指定结果指针值。 否则,如果原始指针值指向对象a,并且存在类型b(忽略cv限定)的对象T,该指针可以与{ {1}},结果是指向a 的指针。否则,转换后指针值将保持不变。

类似地,

b
如果T *aUP = reinterpret_cast<T *>(aU); 是一个标准版式的联合,则

在C ++ 17中将得到很好的定义,并且在即将到来的基于当前标准草案的C ++版本中看起来通常会被很好地定义,其中一个联合其成员之一始终是指针可互换的[basic.compound]/4.2

以上所有都不相关,因为,

Union<T>

T valueS = aSP[9];

不管如何都会调用未定义的行为。 T valueU = aUP[9]; aSP[9](根据定义)分别与aUP[9]*(aSP + 9) [expr.sub]/1相同。这些表达式中的指针算术服从[expr.add]/4

  

将具有整数类型的表达式*(aUP + 9)添加到指针类型的表达式J或从中减去时,结果的类型为P

     
      
  • 如果P的值为空指针值,而P的值为0,则结果为空指针值。
  •   
  • 否则,如果J指向具有 n 个元素的数组对象P的元素x[i],则表达式x和{{1 }}(其中P + J的值为 j )如果0≤i+j≤n指向(可能是假设的)元素J + P如果 0≤i−j≤n ,则表达式J指向(可能是假设的)元素x[i+j]
  •   
  • 否则,行为是不确定的。
  •   

P - Jx[i−j]不指向数组的元素。即使aSPaUP可以与aSP进行指针互换,您也只能访问元素0并计算(但不能访问)元素1的地址。假设的单元素数组…

答案 1 :(得分:4)

因此,如果我们查看reinterpret_casthere)的文档

  

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

现在,别名规则怎么说?

  

每当尝试读取或修改存储的值时,   动态类型类型的对象通过AliasedType类型的glvalue,   除非满足以下条件之一,否则行为是不确定的:

     
      
  1. AliasedType和DynamicType相似。
  2.   
  3. AliasedType是DynamicType的(可能是cv限定的)带符号或无符号的变体。
  4.   
  5. AliasedType是std :: byte,(从C ++ 17开始)char或unsigned char:这允许检查任何对象的对象表示形式为   字节数组。
  6.   

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

类似:

  

非正式地,如果忽略顶层,则两种类型相似   简历资格:

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

然后,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. 

答案 2 :(得分:0)

我找到了c++ - Is sizeof(T) == sizeof(int)。这指定结构不必具有与其元素相同的大小( sigh )。至于工会,可能同样适用(阅读答案后,我被认为是这样)。仅使这种情况成为必需。但是,如果在https://stackoverflow.com/a/21515546中使用sizeof(Struct) == sizeof(T)和“这是公认的”,则指向aSP [9]的指针将与aS [9]处于同一位置(至少我认为是) ,并根据标准保证重新解释广播(根据https://stackoverflow.com/a/21509729中的引用)。

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