On the term "(strict) aliasing violation" relating to class member access

wal*_*nut 8 c++ strict-aliasing language-lawyer reinterpret-cast

This question refers to the current C++20 draft. The quoted passages have been slightly modified from previous standard iterations, but not in relevant ways as far as I know.

I am looking for clarification on the the terms "aliasing violation" or "strict aliasing violation".

My previous impression was that these terms refer specifically to violations of the standard paragraph [basic.lval]/11:

If a program attempts to access ([defns.access]) the stored value of an object through a glvalue whose type is not similar ([conv.qual]) to one of the following types the behavior is undefined:

  • the dynamic type of the object,
  • a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
  • a char, unsigned char, or std?::?byte type.

If a program invokes a defaulted copy/move constructor or copy/move assignment operator for a union of type U with a glvalue argument that does not denote an object of type cv U within its lifetime, the behavior is undefined. [ Note: Unlike in C, C++ has no accesses of class type. — end note ]

The note at the end is clarified further through notes and normative references in [defns.access] to mean that only scalar types can be accessed. See also the notes section on the related cppreference page.

Therefore it seems to me that the paragraph can never apply to access of non-static members of classes through the wrong class type and that "(strict) aliasing violation" cannot apply e.g. to the following example:

struct A { void f() {}; int a; };
struct B { void f() {}; int a; };

int main() {
    auto a = new A; // aligned for all non-overaligned types
    B& b = reinterpret_cast<B&>(*a);
    b.f();   //1
    b.a = 1; //2
}
Run Code Online (Sandbox Code Playgroud)

Assume that sizeof(A) == sizeof(B) and offsetof(A, a) == offsetof(B, b) (although I don't think either makes a difference and the latter is trivial because the classes are standard-layout). Another variation of interest would be struct A { };, but again I think there won't be a difference.

In the case of b.f() there is no access to any scalar object at all. According to [expr.ref], in the latter case the referenced object is supposed to be the data member a of the B object referenced by b, but since that clearly doesn't exist, I suppose forming the member access might already be undefined behavior?

Both //1 and //2 should clearly be undefined behavior in some way though. I think //1 violates [class.mfct.non-static]/2, but I am not exactly sure which paragraph //2 violates exactly.

Which standard paragraphs do the two lines //1 and //2 violate exactly and is this violation considered to be covered by the terms "(strict) aliasing violation" as well?

Jan*_*tke 1

什么是别名

我正在寻求有关术语“别名违规”“严格别名违规”的澄清。

这会很困难,因为这些术语在 C++ 中是模糊且口语化的。规范措辞中唯一使用的是[valarray.syn] p2,这是一个几乎毫无意义的段落。

[basic.lval] p11(您引用的段落)有一个有点相关的脚注:

此列表的目的是指定对象可以或不能使用别名的情况。

一般来说,别名是指通过两个指针或引用,可以到达重叠的内存,从而使两个内存区域可以互相干扰。restrict更正式地说,与 C 中的完全相反。

为了减少可能的别名数量,C++ 使得egint*float*不能互相别名,因为通过int类型的左值访问float将是UB。

另请参阅什么是严格别名规则以及我们为什么关心?

访问的细微差别

最后的注释通过[defns.access]中的注释和规范引用进一步澄清,表示只能访问标量类型。

当前的措辞是“只​​能使用标量类型的泛左值来访问对象。”。

struct A { void f() {}; int a; };
struct B { void f() {}; int a; };
Run Code Online (Sandbox Code Playgroud)

在您的示例中,可以访问AB。如果您编写了x.awhere xis of type ,那么您将通过 type 的泛左值和 type 的泛左值A访问类型的对象,尽管只有前者“用于”访问本身。AintA

这是委员会意图非常明确的案例之一,并且A不应B互相混淆。然而,措辞并没有充分传达这一意图。

另请参阅编辑拉取请求#4777,它更改了注释中“访问”周围的措辞。

您的示例中未定义的行为

让我们看一下代码的稍微简化版本:

int main() {
    A a;
    B& b = reinterpret_cast<B&>(a);
    b.f();   //1
    b.a = 1; //2
}
Run Code Online (Sandbox Code Playgroud)

//1是未定义的行为,因为

可以为其类类型的对象调用非静态成员函数,也可以为从其类类型派生的类 ([class.衍生]) 的对象调用非静态成员函数 [...]

- [class.mfct.non.static] p1

调用的时候b.f(),this调用了B::f(),但是对象实际上是type A,而不是type B这使得它因省略1)而成为 UB 。

//2是未定义的行为,因为

如果程序尝试通过其类型与以下类型之一不相似的泛左值访问对象的存储值,则行为未定义: - [...]

- [基本.lval] p11

b.a = 1

  • A通过 type 的泛左值b访问type 的对象B,以及
  • A::a通过b.a类型 的泛左值访问其子对象int

第二部分还可以;第一部分是未定义的行为。当您忘记注释时(无论如何这不是规范的),这一点是显而易见的:

  • A显然,正在访问类型的对象,并且
  • 显然,这是通过b类型的左值B(虽然不是直接的,但这不是必需的)

将其视为 UB 的另一种方法是查看[expr.ref] p6.2并考虑在 中,如果没有正确的E1.E2类型,指定的子对象就不存在,因此它将是 UBE1省略。

Either way, the intention of the committee and implementers is that this is undefined behavior in some way.


1) There used to be another paragraph that makes it UB if the the object doesn't have the right type, but that was redundant, and everything is already said up here.