通过重新解释强制转换创建无效引用

Mic*_*val 6 c++ standards strict-aliasing language-lawyer reinterpret-cast

我试图确定以下代码是否调用未定义的行为:

#include <iostream>

class A;

void f(A& f)
{
  char* x = reinterpret_cast<char*>(&f);
  for (int i = 0; i < 5; ++i)
    std::cout << x[i];
}

int main(int argc, char** argue)
{
  A* a = reinterpret_cast<A*>(new char[5])
  f(*a);
}
Run Code Online (Sandbox Code Playgroud)

我的理解是,reinterpret_casts to和from char*是兼容的,因为标准允许别名charunsigned char指针(强调我的):

如果程序试图通过不同于以下类型之一的左值访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 一个cv限定版本的动态类型的对象,
  • 与对象的动态类型对应的有符号或无符号类型的类型,
  • 一种类型,是有符号或无符号类型,对应于对象动态类型的cv限定版本,
  • 一种聚合或联合类型,包括其成员中的上述类型之一(包括递归地,子聚合或包含联合的成员),
  • 一个类型,它是对象动态类型的(可能是cv限定的)基类类型,
  • a charunsigned char类型.

但是,我不确定是否f(*a)通过创建A&对无效指针的引用来调用未定义的行为.决定因素似乎是"尝试访问"措辞意味着在C++标准的背景下.

我的直觉是,这并不能构成一个访问,由于访问都需要A进行定义(现声明,但在这个例子中没有定义).不幸的是,我在C++标准中找不到"访问"的具体定义:

是否f(*a)调用未定义的行为?什么构成C++标准中的"访问"?

我理解,无论答案如何,在生产代码中依赖此行为可能是个坏主意.我问这个问题主要是为了提高我对语言的理解.

[编辑] @SergeyA引用了本节的标准.我把它包括在这里以便于参考(强调我的):

5.3.1/1 [expr.unary.op]

一元运算*符执行间接:它所应用的表达式应该是指向对象类型的指针,或指向函数类型的指针,结果是引用表达式指向的对象或函数的左值.如果表达式的类型是"指向T",则结果的类型为" T."[注意:通过指向不完整类型(cv void除外)的指针间接有效.由此获得的左值可以以有限的方式使用(例如,初始化参考); 这个左值不能转换为prvalue,见4.1. - 结束说明]

跟踪4.1的引用,我们发现:

4.1/1 [conv.lval]

T可以将非函数非数组类型的glvalue(3.10)转换为prvalue.如果T是不完整类型,则需要进行此转换的程序格式不正确.如果T是非类类型,则prvalue的类型是cv-nonqualified version T.否则,prvalue的类型是T.

将左值到右值转换应用于表达式时e,可以:

  • e 没有潜在的评估,或
  • 的评价e中的成员的评价结果ex的一组的潜在结果的e,和ex名称的变量x未ODR使用的由ex(3.2)

不访问引用对象中包含的值.

我认为我们的答案在于是否*a满足第二个要点.我在解析这个条件时遇到了麻烦,所以我不确定.

Ser*_*eyA 5

char* x = reinterpret_cast<char*>(&f);已验证。x或者,更具体地说,允许访问- 强制转换本身始终有效。

A* a = reinterpret_cast<A*>(new char[5])无效 - 或者,准确地说,访问a将触发未定义的行为。

这样做的原因是,虽然可以通过char*,但通过随机对象访问字符数组是不行的。标准允许第一个,但不允许第二个。

或者,通俗地说,您可以为type*through指定别名char*,但不能为char*through指定别名type*

编辑

我只是注意到我没有回答直接问题(“什么构成了 C++ 标准中的“访问” ”)。显然,标准没有定义访问(至少,我找不到正式的定义),但取消引用指针通常被理解为有资格访问。