将结构别名作为其第一个成员的别名是严格的别名违规吗?

时间:2018-05-17 04:06:17

标签: c++ language-lawyer strict-aliasing reinterpret-cast

示例代码:

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)char,unsigned char或std :: byte type。
  •   

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

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

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

3 个答案:

答案 0 :(得分:16)

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

  

“指向 cv1 void的指针”的prvalue可以转换为“指向 cv2 T的指针”的prvalue,其中T是对象类型, cv2 与cv资格相同,或者比 cv1 更高的cv资格。如果是原件   指针值表示内存中字节的地址A,而A不满足T的对齐要求,则结果指针值未指定。 否则,如果原始指针值指向对象a,并且存在类型为b的对象T(忽略cv-qualification),那么指针 - 与a相互转换,结果是指向b的指针。否则,指针值不会因转换而改变。

pointer-interconvertible 的定义是:

  

如果出现以下情况,则两个对象a和b是指针可互换的:

     
      
  • 它们是同一个对象,或
  •   
  • 一个是union对象,另一个是该对象的非静态数据成员,或
  •   
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有非静态数据成员,则该对象的第一个基类子对象,或者< / LI>   
  • 存在一个对象c,使得a和c是指针可互换的,而c和b是指针可互换的。
  •   

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

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

答案 1 :(得分:5)

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

  

可以强制转换类型为T1的glvalue表达式,指定对象ht = 3 wd1 = 3 wd2 = 9 gap = 0.5 nc = ncol(mydata) nr = nrow(mydata) x = rep(c(seq(0,(nc-2)*(wd1+gap), wd1+gap), (nc-2)*(wd1+gap) + gap + 0.5*(wd2+wd1)), nr) y = rep(seq(0,(nr-1)*(ht+gap), ht+gap), nc) %>% sort() h = rep(ht, nr * nc) w = rep(c(rep(wd1, nc-1), wd2), nr) info = as.vector(t(as.matrix(mydata[nr:1,]))) df = data.frame(x = x, y = y, h = h, w = w, info = info) ggplot(df, aes(x, y, height = h, width = w, label = info)) + geom_tile() + geom_text(color = "white", fontface = "bold") + coord_fixed() + scale_fill_brewer(type = "qual",palette = "Dark2") + theme_void() + guides(fill = F)   如果类型为“指向T1的指针”的表达式,则为“对T2的引用”   可以使用a显式转换为类型“指向T2的指针”   reinterpret_cast的。结果是x的结果   其中*reinterpret_­cast<T2 *>(p)是指向“指向T1”p的指针。暂时没有   创建,没有复制,没有构造函数或   转换函数称为 [1]

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

支持@ M.M关于指针不可侵犯的答案

来自cppreference

  

假设满足对齐要求,x就可以了   更改少数限制案例之外的指针值   处理指针可互换的对象:

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

答案 2 :(得分:0)

所引用的规则源于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 ++标准在某种程度上清除了问题,但也没有真正解决。