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

Strict Aliasing Rule and Type Aliasing in C++

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

Type aliasing

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

  • AliasedType and DynamicType are similar.
  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
  • AliasedType is std::byte, (since C++17)char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

我还发现了一个 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 是聚合(如果是的话,我必须改变什么才能使其成为未定义的行为)?

任何提示和帮助将不胜感激。

EDIT 关于重复的问题

我知道 post,但我没看到他们在哪里阐明哪些类型相似。至少没有达到我能理解的程度。因此,如果您不关闭这个问题,我们将不胜感激。

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

8.2.1 Value category [basic.lval]

11 If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined: 63

(11.1) — the dynamic type of the object,

(11.2) — a cv-qualified version of the dynamic type of the object,

(11.3) — a type similar (as defined in 7.5) to the dynamic type of the object,

(11.4) — a type that is the signed or unsigned type corresponding to the dynamic type of the object,

(11.5) — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

(11.6) — an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union),

(11.7) — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

(11.8) — a char, unsigned char, or std::byte type


63) The intent of this list is to specify those circumstances in which an object may or may not be aliased.

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

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

A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template ([temp.names]), and then followed by an id-expression, is a postfix expression. The postfix expression before the dot or arrow is evaluated;[67] the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.

[67] If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

大胆的矿

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

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

Informally, two types are similar if, ignoring top-level cv-qualification:

  • they are the same type; or
  • they are both pointers, and the pointed-to types are similar; or
  • they are both pointers to member of the same class, and the types of the pointed-to members are similar; or
  • they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar.

For example:

  • const int * volatile * and int * * const are similar;
  • const int (* volatile S::* const)[20] and int (* const S::* volatile)[20] are similar;
  • int (* const *)(int *) and int (* volatile *)(int *) are similar;
  • int (S::*)() const and int (S::*)() are not similar;
  • int (*)(int *) and int (*)(const int *) are not similar;
  • const int (*)(int *) and int (*)(int *) are not similar;
  • int (*)(int * const) and int (*)(int *) are similar (they are the same type);
  • std::pair<int, int> and std::pair<const int, int> are not similar.

This rule enables type-based alias analysis, in which a compiler assumes that the value read through a glvalue of one type is not modified by a write to a glvalue of a different type (subject to the exceptions noted above).

Note that many C++ compilers relax this rule, as a non-standard language extension, to allow wrong-type access through the inactive member of a union (such access is not undefined in C