繁体   English   中英

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

[英]Strict Aliasing Rule and Type Aliasing in C++

在违反严格的别名规则时,我试图了解未定义的行为。 为了理解它,我已经阅读了很多关于 SO 的文章。 然而,一个问题仍然存在:我真的不明白什么时候两种类型非法别名。 cpp-reference状态:

类型别名

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

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

我还在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是聚合(如果是,我必须更改什么才能使其成为未定义的行为)?

任何单挑和帮助将不胜感激。

编辑关于被重复的问题

我知道这篇文章,但我没有看到他们在哪里澄清了哪些类型是相似的。 至少不会达到我能理解的程度。 因此,如果您不关闭这个问题,那就太好了。

不,这是不合法的,您有未定义的行为:

8.2.1 值类别[basic.lval]

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

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

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

(11.3) — 类似于(如 7.5 中定义的)对象的动态类型的类型,

(11.4) — 与对象的动态类型相对应的有符号或无符号类型,

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

(11.6) — 在其元素或非静态数据成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的元素或非静态数据成员),

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

(11.8) — char、unsigned char 或 std::byte 类型


63) 此列表的目的是指定对象可以或不可以别名的情况。

在表达式b->b = a; 未定义的行为不是由于赋值,而是由于类成员访问表达式b->b 如果这个表达式不是 UB,你的代码就不是 UB。

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

后缀表达式后跟一个点。 或一个箭头 ->,可选地后跟关键字模板 ([temp.names]),然后后跟一个 id 表达式,是后缀表达式。 计算点或箭头之前的后缀表达式;[67]该计算的结果与 id 表达式一起决定了整个后缀表达式的结果。

[67] 如果对类成员访问表达式求值,即使结果不需要确定整个后缀表达式的值,例如如果 id 表达式表示静态成员,也会发生子表达式求值。

大胆的我

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

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

非正式地,两种类型是相似的 if,忽略顶级 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>不相似。

此规则启用基于类型的别名分析,其中编译器假定通过一种类型的泛左值读取的值不会因写入不同类型的泛左值而被修改(除上述例外情况外)。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM