【问题标题】:Pointer casting between types and undefined behavior类型和未定义行为之间的指针转换
【发布时间】:2015-08-20 02:31:07
【问题描述】:

在 C++ 中,如果 AB 是不同的类,则将 A* 强制转换为 B* 然后取消引用(即给定)通常是未定义的行为

A *a = ...;

写是未定义的行为

((B*)a)->x

即使x 在两个类中具有相同的类型和偏移量也是如此。

另一方面,char* 对强制转换规则有特殊豁免。这样写是不是就说明定义明确了?

((B*)(char*)a)->x

【问题讨论】:

  • 它仍然是未定义的行为。
  • 不。对不起。通过char 投射并不会神奇地使其合法。
  • 类或结构成员的偏移量通常保证为类或结构的第一个成员:它将与结构具有相同的地址,但即使是这个一般规则也可能违反如果类或结构包含虚函数,可能位于第一个成员之前。
  • @rwallace 主要的限制是别名,而不是强制转换。以任何你喜欢的方式投射它,它不会改变你违反别名规则。
  • @rwallace - 既然是C++,为什么不先定义一个基类和普通成员,然后用派生类来处理非普通成员呢?

标签: c++ casting undefined-behavior


【解决方案1】:

这个问题取决于AB的类型。

如果BA 的基类,或者AB 的基类,那么((B *)a) 是一个静态转换,所以((B *)a)->x 是正确的,如果B 是基类;或者如果B 是派生类并且a 实际上指向B 类型对象的A 部分。

如果AB 是不相关的类型,那么我们就得看看严格的别名规则了。这是一些具体的代码:

struct A { int p,q,r; } a;
struct B { int z,y; } b;
static_assert( sizeof a == 3 * sizeof(int) );
static_assert( sizeof b == 2 * sizeof(int) );

假设断言通过,我们知道((B *)&a)->y 必须指定与a.q 相同的内存位置。

这里有两种思想流派:

  1. ((B *)&a)->yint 类型的左值,a.qint 类型的对象。 intint 兼容,因此不存在别名冲突。

  2. 求值((B *)&a)->y意味着(*(B *)&a).y,它首先求值*(B *)&a,这违反了严格的别名规则,因为*(B *)&a没有指定包含B的内存位置。

    李>

我个人选择(1);我不认为案例 2 中的评估算作“访问”,严格的别名规则谈到“访问”:

如果程序尝试通过非下列类型之一的左值来访问对象的存储值,则行为未定义:

但是有一个灰色区域,因为虽然读取((B *)&a)->y 访问的是int 对象的存储值;目前尚不清楚它是否也被视为访问包含该对象的结构的存储值的一部分。


((B*)(char*)a)->x 与后一种情况没有区别,我们仍然会得到int 类型的左值。在继承的情况下,如果是多重继承,那么这可能会破坏代码,因为它将static_cast 变成reinterpret_cast

【讨论】:

  • 如果代码写成*((int*)&((B *)&a)->y)会影响事情吗?获取(B *)&a)->y 地址的行为不应构成“访问”,一旦获取地址,将其用作int* 应符合别名规则,因为它就是这样。
  • 它不会影响事情 - (B *)&a)->y 已经有类型 int。别名规则涉及进行访问的左值的类型和被访问对象的有效类型,而不是您使用了多少强制转换
  • 鉴于int *temp = (B*)&a->y; 稍后是*temp = whatever;,我不明白第一个语句如何被视为任何类型的非法“访问”,我不知道它使用的任何机制会“污染”结果指针,从而使后续访问变得非法。有没有这样的机制?
  • @supercat 好吧,我同意你的观点,但我试图在我的帖子中指出(由于之前对该主题的讨论)有些人不同意
  • 有什么理由要求*(&(foo->bar))foo->bar 同义吗?我认为标准(或澄清文件)说后者要求目标写为与foo 兼容的类型别名是合理的,而前者不会施加如此严格的要求。跨度>
猜你喜欢
  • 2019-08-21
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
相关资源
最近更新 更多