将结构别名为其第一个成员是否严格违反别名



示例代码:

struct S { int x; };
int func()
{
S s{2};
return (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
}

我认为这是普遍的,被认为是可以接受的。该标准确实保证结构中没有初始填充。但是,这种情况未在严格别名规则 (C++17 [basic.lval]/11) 中列出:

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

  • (11.1)对象的动态类型,
  • (11.2) 对象的动态类型的 CV 合格版本,
  • (11.3) 与对象的动态类型类似的类型(如 7.5 中所定义),
  • (11.4) 与对象的动态类型对应的有符号或无符号类型,
  • (11.5) 一种类型,该类型是对应于对象的动态类型的 CV 限定版本的有符号或无符号类型,
  • (11.6) 在其元素或非静态数据成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含的联合的元素或非静态数据成员),
  • (11.7) 一种类型,它是对象的动态类型的基类类型(可能符合 CV 条件),
  • (11.8) 字符、无符号字符或 std::byte 类型。

很明显,对象s正在访问其存储值。

项目符号中列出的类型是执行访问的 glvalue 的类型,而不是正在访问的对象的类型。 在此代码中,glvalue 类型是int它不是聚合或联合类型,排除了 11.6。

我的问题是:这段代码是否正确,如果是,在上述哪些要点下是允许的?

演员表的行为归结为 [expr.static.cast]/13;

类型为"指向cv1void的指针"的 prvalue 可以转换为"指向 cv2T的指针"类型的 prvalue,其中T是对象类型,cv2cv1相同或更高的 cv 限定。如果原件 指针值表示内存中一个字节的地址AA不满足T的对齐要求,则未指定生成的指针值。否则,如果原始指针值指向对象a,并且存在类型为T(忽略 cv-限定)的对象b可与a指针相互转换,则结果是指向b的指针。否则,指针值在转换时保持不变。

指针互转换的定义是:

在以下情况下,两个对象 a 和 b 是指针可相互转换的:

  • 它们是同一对象,或者
  • 一个是联合对象,另一个是该对象的非静态数据成员,或者
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,
  • 或者,如果对象没有非静态数据成员,则为该对象的第一个基类子对象,或者
  • 存在一个对象 C,使得 A 和 C 是指针可相互转换的,C 和 B 是指针可相互转换的。

因此,在原始代码中,ss.x指针可相互转换的,因此(int &)s实际上指定了s.x

因此,在严格的别名规则中,正在访问其存储值的对象是s.x而不是s,因此没有问题,代码是正确的。

我认为它在expr.reinterpret.cast#11中

可以强制转换类型 T1 的 glvalue 表达式,指定对象x如果类型为"指向 T1 的指针"的表达式,则类型为"对 T2 的引用">可以使用reinterpret_cast。结果是*reinterpret_­cast<T2 *>(p)其中p是指向类型为"指向 T1 的指针">x的指针。没有临时是 创建,不创建副本,也不创建构造函数或 转换函数称为[1]。

[1] 当结果引用与源 glvalue 相同的对象时,这有时被称为类型双关

语支持@M.M关于指针不可掩蔽的答案:

从 CPP 首选项:

假设满足对齐要求,则reinterpret_cast可以少数有限情况之外不更改指针的值 处理指针可转换对象:

struct S { int a; } s;

int* p = reinterpret_cast<int*>(&s); // value of p is "pointer to s.a" because s.a
// and s are pointer-interconvertible
*p = 2; // s.a is also 2

struct S { int a; };
S s{2};
int i = (int &)s;    // Equivalent to *reinterpret_cast<int *>(&s)
// i doesn't change S.a;

引用的规则源自C89中的类似规则,除非人们扩展"by"一词的含义,或者认识到C89编写时"未定义的行为"的含义,否则该规则将是荒谬的。 给定类似struct S {unsigned dat[10];}s;的东西,语句s.dat[1]++;会清楚地修改s的存储值,但是该表达式中唯一的类型struct S的左值仅用于生成类型unsigned*的值。 用于修改任何对象的唯一左值是int类型。

在我看来,有两种相关的方法可以解决这个问题:(1)认识到标准的作者希望允许一种类型的标签值明显来自另一种类型的情况,但不想纠结于必须解释哪些形式的可见推导的细节, 特别是因为编译器需要识别的情况范围会根据他们执行的优化风格和使用它们的任务而有很大差异;(2)认识到标准的作者没有理由认为,如果每个人都清楚有理由不这样做,那么标准是否真的要求对特定结构进行有用的处理就无关紧要。

我认为委员会成员之间对于编译器是否给出以下内容没有达成共识:

struct foo {int ct; int *dat;} it;
void test(void)
{
for (int i=0; i < it.ct; i++)
it.dat[i] = 0;
}

应该要求确保例如在it.ct = 1234; it.dat = &it.ct;之后,对test();的调用将it.ct归零并且没有其他效果。 部分理由表明,至少有一些委员会成员会预料到这一点,但省略了允许使用任意成员类型的左值访问结构类型的对象的规则,这表明情况并非如此。 C标准从未真正解决这个问题,C++标准在一定程度上清理了问题,但也没有真正解决它。

最新更新