【问题标题】:Can we access a member of a non-existing union?我们可以访问不存在的工会的成员吗?
【发布时间】:2019-04-08 14:45:32
【问题描述】:

在 c++ 标准中,[basic.lval]/11.6 中说:

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

  • 一种聚合或联合类型,在其元素或非静态数据成员(递归地包括子聚合或包含联合的元素或非静态数据成员)中包含上述类型之一,[...]

这句话是严格别名规则的一部分。

它可以让我们访问不存在的工会的非活动成员吗?如:

struct A{
  int id :1;
  int value :32;
  };
struct Id{
  int id :1;
  };

union X{
  A a;
  Id id_;
  };

void test(){
  A a;
  auto id = reinterpret_cast<X&>(a).id_; //UB or not?
  }

注意:下面解释一下我在标准中没有掌握的内容,以及为什么上面的示例可能有用。

我想知道 [basic.lval]/11.6 有什么用处。

[class.mfct.non-static]/2 确实禁止我们调用“强制转换为”联合或聚合的成员函数:

如果为非 X 类型或从 X 派生的类型的对象调用 X 类的非静态成员函数,则行为未定义。

考虑到静态数据成员访问或静态成员函数可以直接使用 qualified-name (a_class::a_static_member) 执行, [basic.lval]/11.6 唯一有用的用例可能是访问“casted to”联合的成员。我考虑过使用最后一条标准规则来实现“优化变体”。该变体可以包含 A 类对象或 B 类对象,两者都以大小为 1 的位域开头,表示类型:

class A{
  unsigned type_id_ :1;
  int value :31;
  public:
  A():type_id_{0}{}
  void bar{};
  void baz{};
  };

class B{
  unsigned type_id_ :1;
  int value :31;
  public:
  B():type_id_{1}{}
  int value() const;
  void value(int);
  void bar{};
  void baz{};
  };

struct type_id_t{
  unsigned type_id_ :1;
  };

struct AB_variant{
  union {
    A a;
    B b;
    type_id_t id;};
    //[...]
  static void foo(AB_variant& x){
    if (x.id.type_id_==0){
      reinterpret_cast<A&>(x).bar();
      reinterpret_cast<A&>(x).baz();
      }
    else if (x.id.type_id_==1){
      reinterpret_cast<B&>(x).bar();
      reinterpret_cast<B&>(x).baz();
      }
    }
 };

AB_variant::foo 的调用不会调用未定义的行为,只要它的参数引用AB_variant 类型的对象,这要归功于指针互转换 规则[basic.compound]/4。允许访问非活动联合成员type_id_,因为id 属于ABtype_id_t[class.mem]/25公共初始序列

但是如果我尝试使用A 类型的完整对象调用它会发生什么?

A a{};
AB_variant::foo(reinterpret_cast<AB_variant&>(a));

这里的问题是我试图访问一个不存在的联合的非活动成员。

两个相关的标准段落是[class.mem]/25:

在具有结构类型 T1 的活动成员的标准布局联合中,允许读取结构类型 T2 的另一个联合成员的非静态数据成员 m,前提是 m 是 T1 的公共初始序列的一部分,并且T2;就好像T1的相应成员被提名了一样。

还有[class.union]/1:

在联合中,如果其名称引用的对象其生命周期已开始但尚未结束,则非静态数据成员处于活动状态。

Q3:“它的名字所指”这个表达是否意味着“一个对象”实际上是一个构建在一个活的联合体中的对象?还是因为 [basic.lval]/11.6 而引用对象 a

【问题讨论】:

  • Q1, Q2... Q3... 这不是符合 SO 的“过于宽泛”的定义吗?
  • @StoryTeller 我认为它们只是问题的结构。
  • 不幸的是,这可能更像是一个讨论问题,而不是一个可能有特定答案的问题,所以我倾向于同意:“太宽泛了”。
  • 第一个示例似乎是明显的严格别名违规,因为您取消引用指向A 的指针实际上是指向int 的指针。 (实际上它看起来像一个错字,应该是reinterpret_cast&lt;A*&gt;(&amp;j),甚至不能按原样编译)
  • 那么请将您的问题限制在与标题直接相关的内容上。其他一切都只是臃肿。

标签: c++ language-lawyer unions strict-aliasing class-members


【解决方案1】:

[expr.ref]/4.2 定义了E1.E2 的含义,如果E2 是一个非静态数据成员:

如果 E2 是非静态数据成员 [...],则 表达式指定由指定的对象的命名成员 第一个表达式。

这仅在第一个表达式实际指定对象的情况下定义行为。由于在您的示例中,第一个表达式未指定任何对象,因此行为未通过遗漏定义;请参阅[defns.undefined](“当本文档省略任何明确的行为定义时,可能会出现未定义的行为......”)。


您还误解了严格别名规则中“访问”的含义。它的意思是“读取或修改对象的值”([defns.access])。命名非静态数据成员的类成员访问表达式既不读取也不修改任何对象的值,因此不是“访问”,因此永远不会有“访问...通过”“聚合或联合”的左值type" 由类成员访问表达式引起的。

[basic.lval]/11.6 本质上是从 C 中复制而来的,它实际上意味着某些东西,因为分配或复制 structunion 会访问整个对象。它在 C++ 中没有意义,因为类类型的赋值和复制是通过特殊的成员函数执行的,这些函数要么执行成员复制(因此单独“访问”成员),要么对对象表示进行操作。见core issue 2051

【讨论】:

  • 这个问题可能已经失去一致性,因为我不得不拆分它。实际上第一部分是here。你会看到我提到了什么是访问。我的问题是“指定”或“名称指代”等表达方式......我应该将名称视为对象的身份(实体的身份)吗?在这种情况下,“名称”指定并且只能指定成员子对象,而不能指定碰巧位于同一位置的对象?
  • 所以这个核心问题也是另一个问题的答案,什么“在什么 [basic.lval]/11.6 中可能有用?=> 在 c++ 中什么都不回答?
  • 我认为 C 中的“别名规则”以及 C++ 中的派生规则的预期含义是通过成员访问左值进行的访问,或者新派生自的指针它应该被视为(出于“别名规则”的目的),就好像它是通过父左值进行的访问一样。因此,对从联合左值新派生的左值的访问将成为通过该联合左值的访问,进而允许访问所有其他联合成员。因此,需要“父左值可以访问子项”规则来允许此类访问。
  • @supercat 因此,该规则旨在与“是”规则一样不对称。我在我的示例代码中所做的是在不预期的方向上使用它,不是吗?
  • @supercat 类似于 OP 的代码已经在 CWG 反射器上提出,这个答案与那里给出的答案是一致的,所以我相信它准确地反映了 C++ 标准措辞的当前解释编译器实现者。
【解决方案2】:

有很多情况,特别是涉及类型双关语和联合,其中 C 或 C++ 标准的一部分描述了某些动作的行为,另一部分将重叠类的动作描述为调用 UB,重叠区域包括一些应该由所有实现以及至少在某些实现上支持不切实际的其他实现一致地处理的操作。该标准的作者并没有试图完全描述所有应该按照定义处理的情况,而是希望实现能够维护基本原理中描述的 C 精神,包括“不要阻止程序员做需要做的事情”的原则要做”。这通常会导致质量实施在必要时优先考虑行为的定义以满足客户的需求,同时在允许优化同时满足客户需求时优先考虑行为的“未定义”。

将 C 或 C++ 标准视为定义有用语言的唯一方法是识别其行为由标准的一部分描述并被另一部分归类为 UB 的动作类别,并识别在该标准中对动作的处理类别作为标准管辖范围之外的实施质量问题。该标准的作者希望编译器编写者对其客户的需求敏感,因此没有将行为定义和未定义之间的冲突视为特定问题。因此,他们认为没有必要以可以一致应用而不会产生此类冲突的方式来定义诸如“对象”、“左值”、“生命周期”和“访问”之类的术语,因此他们创建的定义不能用于决定存在此类冲突时是否应定义特定操作。

因此,除非或直到标准认可更多与对象相关的概念和访问对象的方式,否则旨在适合某种目的的质量实现是否应该支持特定操作的问题将取决于其作者是否应该期望认识到该操作将对此目的有用。

【讨论】:

    猜你喜欢
    • 2019-04-08
    • 1970-01-01
    • 1970-01-01
    • 2020-12-24
    • 2021-12-11
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2022-03-18
    相关资源
    最近更新 更多