在c ++标准中,[basic.lval]/11.6中说:
如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:[...]
- 一种聚合或联合类型,其元素或非静态数据成员(递归包括子聚合或所包含的联合的元素或非静态数据成员)中包括上述类型之一,[...]
此句子是 strict-aliasing 规则的一部分。
它可以允许我们访问不存在的工会的不活跃成员吗?如:
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类的非静态成员函数,则该行为是不确定的。
考虑到可以使用限定名称(a_class::a_static_member
)直接执行静态数据成员访问或静态成员功能,
[basic.lval] /11.6唯一有用的用例可能是访问“强制转换为”联合的成员。我考虑过使用最后一个标准规则来实现“优化变体”。此变体可以容纳一个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();
}
}
};
由于 pointer-interconvertibility的规则,对AB_variant::foo
的调用不会调用未定义的行为,只要其参数引用类型为AB_variant
的对象 [basic.compound]/4。允许访问无效的联合成员type_id_
,因为id
属于A
,B
和{{1}的公共初始序列 } [class.mem]/25:
但是,如果我尝试使用类型为type_id_t
的完整对象来调用它,会发生什么?
A
这里的问题是我尝试访问不存在的联合的非活动成员。
两个相关的标准段落为[class.mem]/25:
在具有结构类型T1的活动成员的标准布局联合中,允许读取结构类型T2的另一个联合成员的非静态数据成员m,条件是m是T1的公共初始序列的一部分,并且T2;行为就像提名了T1的相应成员一样。
在联合中,如果非静态数据成员的名称引用其生存期已开始且尚未结束的对象,则该数据成员处于活动状态。
第3季度:“其名称所指”一词是否表示“对象”实际上是在活着的联合体内建立的对象?或者由于[basic.lval] /11.6而引用对象A a{};
AB_variant::foo(reinterpret_cast<AB_variant&>(a));
。
答案 0 :(得分:2)
有很多情况,特别是涉及类型修剪和联合的情况,其中C或C ++标准的一部分描述了某些操作的行为,另一部分描述了作为UB调用的重叠操作类,并且重叠区域包括一些所有实现以及不支持至少某些实现不可行的其他行为应一致处理的操作。该标准的作者并未试图完全描述应按定义来对待的所有情况,而是希望实现会坚持《基本原理》中描述的C精神,包括“不要阻止程序员按照需要做的事情”的原则。待完成”。通常,这将导致质量实施在满足客户需求时优先考虑行为的定义,而在行为“未定义”时优先考虑行为的定义,这将使优化也可以满足客户的需求。
将C或C ++标准定义为一种有用的语言的唯一方法是,识别一类行为,其行为由标准的一个部分描述,而另一部分归为UB,并在该过程中认识到对行为的处理将其归类为标准管辖范围之外的实施质量问题。该标准的作者期望编译器作者对客户的需求敏感,因此不会将行为定义和未定义之间的冲突视为特定问题。因此,他们认为无需以可以一致地应用而不会产生此类冲突的方式来定义“对象”,“左值”,“生命周期”和“访问”之类的术语,因此他们创建的定义不可用于决定目的。存在此类冲突时是否应定义特定的动作。
因此,除非或直到标准认识到与对象相关的更多概念以及访问对象的方式,否则是否应该预期旨在适合某个目的的质量实施是否支持某种行动的问题将取决于其作者。应该期望认识到该操作对于此目的将是有用的。
答案 1 :(得分:2)
[expr.ref]/4.2定义了E1.E2
是非静态数据成员时E2
的含义:
如果
E2
是非静态数据成员[...],则 表达式指定由指定的对象的命名成员 第一个表达式。
这仅在第一个表达式实际上指定一个对象的情况下定义行为。由于在您的示例中,第一个表达式未指定任何对象,因此行为未通过遗漏定义;参见[defns.undefined](“当本文档省略行为的任何明确定义时,可能会出现未定义的行为...”。)
您还误解了严格别名规则中“访问”的含义。这意味着“读取或修改对象的值”([defns.access])。命名非静态数据成员的类成员访问表达式既不读取也不修改任何对象的值,因此不是“访问”,因此永远不会通过“聚集或并集”的glvalue进行“访问...”类型”,这归功于类成员访问表达式。
[basic.lval] /11.6本质上是从C复制的,它实际上表示某种含义,因为分配或复制struct
或union
可以访问整个对象。在C ++中,这是没有意义的,因为类类型的分配和复制是通过特殊的成员函数执行的,这些成员函数要么执行逐成员复制(然后分别“访问”成员),要么对对象表示进行操作。参见core issue 2051。