我们可以访问一个不存在的联盟的成员吗

Can we access a member of a non-existing union?

本文关键字:不存在 一个 联盟 成员 访问 我们      更新时间:2023-10-16

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

如果程序试图通过以下类型以外的glvalue访问对象的存储值,则行为未定义:〔…〕

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

这句话是严格混叠规则的一部分。

它能允许我们访问一个不存在的工会的非活跃成员吗?如:

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?
}

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

我想知道在什么情况下[basic.lval]/11.6会有用。

[class.mfct.non-static]/2确实禁止我们调用"casted to"并集或聚合的成员函数:

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

考虑到静态数据成员访问或静态成员功能可以使用限定名(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

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

如果E2是非静态数据成员[…],则表达式指定由第一个表达式。

这只为第一个表达式实际指定对象的情况定义行为。由于在您的示例中,第一个表达式没有指定对象,因此该行为是通过省略来定义的;参见[defns.definned]("当本文档省略了任何明确的行为定义时,可能会出现未定义的行为…")。


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

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

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

将C或C++标准视为定义有用语言的唯一方法是识别一类行为由标准的一部分描述并由另一部分分类为UB的行为,并将该类行为的处理视为标准管辖范围之外的实施质量问题。该标准的作者希望编译器编写者对客户的需求敏感,因此没有将行为定义和不确定性之间的冲突视为一个特殊的问题。因此,他们认为没有必要定义"对象"、"左值"、"寿命"answers"访问"等术语,这些术语可以在不产生此类冲突的情况下一致应用,因此,他们创建的定义无法用于决定在存在此类冲突时是否应定义特定行动。

因此,除非或直到标准认识到更多与对象相关的概念和访问它们的方式,否则是否应该期望适合某一目的的高质量实施来支持某一行动的问题将取决于其作者是否应该认识到该行动对该目的有用。

相关文章: