C ++中严格的别名规则和类型别名

时间:2018-08-23 11:16:27

标签: c++ language-lawyer strict-aliasing type-punning

当我违反严格的别名规则时,我试图了解未定义的行为。为了理解它,我阅读了许多关于SO的文章。但是,仍然有一个问题:我不太了解两种类型的非法别名。 cpp-reference指出:

  

类型别名

     

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

     
      
  • AliasedType和DynamicType相似。
  •   
  • AliasedType是DynamicType的(可能是经过简历验证的)带符号或无符号的变体。
  •   
  • AliasedType是std :: byte,(从C ++ 17开始)char或unsigned char:这允许将任何对象的对象表示形式检查为字节数组。
  •   

我还发现了一个nice example on SO,可以清楚地看到问题所在:

int foo( float *f, int *i ) { 
    *i = 1;               
    *f = 0.f;            

   return *i;
}

int main() {
    int x = 0;

    std::cout << x << "\n";   // Expect 0
    x = foo(reinterpret_cast<float*>(&x), &x);
    std::cout << x << "\n";   // Expect 0?
}

intfloat是非相似类型,此程序可能会造成严重破坏。我看不到和理解的是以下修改:

struct A
{
    int a;
};

struct B
{
    int b;
};

A foo( A *a, B *b ) { 
    a->a = 1;               
    b->b = 0;            

    return *a;
}

int main() {
    A a;
    a.a = 0;


    std::cout << a.a << "\n";   // Expect 0
    a = foo(&a, reinterpret_cast<B*>(&a));
    std::cout << a.a << "\n";   // Expect 0?
}

是否有AB相似的类型,一切都很好,或者它们是非法别名,并且我的行为不确定。如果合法,这是否是因为AB是聚合(如果是,我将需要更改什么才能使其具有不确定的行为)?

任何注意和帮助将不胜感激。

编辑 关于重复的问题

我知道有this个帖子,但是我看不到他们在哪里澄清相似的类型。至少在某种程度上我不会理解。因此,如果您不关闭此问题,将非常好。

3 个答案:

答案 0 :(得分:2)

不,这是非法的,并且您有未定义的行为:

  

8.2.1值类别[basic.lval]

     

11如果程序尝试访问对象的存储值   通过除以下类型之一以外的值看,   行为未定义: 63

     

(11.1)—对象的动态类型,

     

(11.2)—对象的动态类型的cv限定版本,

     

(11.3)-与动态类型类似的类型(如7.5中所定义)。   对象

     

(11.4)—一种类型,它是与以下类型对应的有符号或无符号类型   对象的动态类型,

     

(11.5)—一种类型,它是对应于a的有符号或无符号类型   cv限定的对象动态类型的版本,

     

(11.6)-包括以下类型之一的聚合或联合类型   元素或非静态数据成员中的上述类型   (包括递归地包括的元素或非静态数据成员   子集合或包含的联合)

     

(11.7)—一种类型(可能是cv限定)的基类类型   对象的动态类型,

     

(11.8)-字符,无符号字符或std :: byte类型

     
     

63)此列表的目的是指定在哪些情况下   一个对象可能会也可能不会被别名。

答案 1 :(得分:1)

在表达式b->b = a;中,未定义的行为不是由于赋值,而是由于类成员访问表达式b->b。如果此表达式不是UB,则您的代码也不会是UB。

[expr.ref]/1中,指定类成员访问构成对对象b的访问(在->的左侧):

  

后缀表达式,后跟一个点。或一个箭头->(可选),后跟关键字模板([temp.names]),再后跟id表达式,是后缀表达式。 对点或箭头之前的后缀表达式进行求值; [67] 该求值的结果与id表达式一起确定整个后缀表达式的结果。

     

[67]如果评估了类成员访问表达式,则即使不需要确定整个后缀表达式的值(例如,如果id-expression表示静态成员),也将进行子表达式评估。

粗体

因此b->b使用类型为a的表达式读取对象B的值,并且您引用的规则在此处适用。

答案 2 :(得分:0)

关于类似的类型,reinterpret_cast部分提供了一些有用的解释和示例:

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

  • 它们是同一类型;或
  • 它们都是指针,指向的类型相似;或
  • 它们都是指向相同类的成员的指针,并且指向的成员的类型相似;或
  • 它们都是相同大小的数组,或者都是边界未知的数组,并且数组元素类型相似。

例如:

  • const int * volatile *int * * const相似;
  • const int (* volatile S::* const)[20]int (* const S::* volatile)[20]相似;
  • int (* const *)(int *)int (* volatile *)(int *)相似;
  • int (S::*)() constint (S::*)()不相似;
  • int (*)(int *)int (*)(const int *)不相似;
  • const int (*)(int *)int (*)(int *)不相似;
  • int (*)(int * const)int (*)(int *)相似(它们是相同的类型);
  • std::pair<int, int>std::pair<const int, int>不相似。

此规则启用基于类型的别名分析,在该分析中,编译器假定通过写入另一类型的glvalue不会修改通过一种类型的glvalue读取的值(受上述异常的影响)。 / p>

请注意,作为非标准语言扩展,许多C ++编译器都放宽了此规则,以允许通过联合的不活动成员进行错误类型的访问(这种访问在C中并非未定义