当我违反严格的别名规则时,我试图了解未定义的行为。为了理解它,我阅读了许多关于SO的文章。但是,仍然有一个问题:我不太了解两种类型的非法别名。 cpp-reference指出:
类型别名
每当尝试通过AliasedType类型的glvalue读取或修改DynamicType类型的对象的存储值时,除非满足以下条件之一,否则行为是不确定的:
- AliasedType和DynamicType相似。
- AliasedType是DynamicType的(可能是经过简历验证的)带符号或无符号的变体。
- AliasedType是std :: byte,(从C ++ 17开始)char或unsigned char:这允许将任何对象的对象表示形式检查为字节数组。
我还发现了一个nice example on SO,可以清楚地看到问题所在:
int foo( float *f, int *i ) {
*i = 1;
*f = 0.f;
return *i;
}
int main() {
int x = 0;
std::cout << x << "\n"; // Expect 0
x = foo(reinterpret_cast<float*>(&x), &x);
std::cout << x << "\n"; // Expect 0?
}
int
和float
是非相似类型,此程序可能会造成严重破坏。我看不到和理解的是以下修改:
struct A
{
int a;
};
struct B
{
int b;
};
A foo( A *a, B *b ) {
a->a = 1;
b->b = 0;
return *a;
}
int main() {
A a;
a.a = 0;
std::cout << a.a << "\n"; // Expect 0
a = foo(&a, reinterpret_cast<B*>(&a));
std::cout << a.a << "\n"; // Expect 0?
}
是否有A
和B
相似的类型,一切都很好,或者它们是非法别名,并且我的行为不确定。如果合法,这是否是因为A
和B
是聚合(如果是,我将需要更改什么才能使其具有不确定的行为)?
任何注意和帮助将不胜感激。
编辑 关于重复的问题
我知道有this个帖子,但是我看不到他们在哪里澄清相似的类型。至少在某种程度上我不会理解。因此,如果您不关闭此问题,将非常好。
答案 0 :(得分:2)
不,这是非法的,并且您有未定义的行为:
8.2.1值类别[basic.lval]
11如果程序尝试访问对象的存储值 通过除以下类型之一以外的值看, 行为未定义: 63
(11.1)—对象的动态类型,
(11.2)—对象的动态类型的cv限定版本,
(11.3)-与动态类型类似的类型(如7.5中所定义)。 对象
(11.4)—一种类型,它是与以下类型对应的有符号或无符号类型 对象的动态类型,
(11.5)—一种类型,它是对应于a的有符号或无符号类型 cv限定的对象动态类型的版本,
(11.6)-包括以下类型之一的聚合或联合类型 元素或非静态数据成员中的上述类型 (包括递归地包括的元素或非静态数据成员 子集合或包含的联合)
(11.7)—一种类型(可能是cv限定)的基类类型 对象的动态类型,
(11.8)-字符,无符号字符或std :: byte类型
63)此列表的目的是指定在哪些情况下 一个对象可能会也可能不会被别名。
答案 1 :(得分:1)
在表达式b->b = a;
中,未定义的行为不是由于赋值,而是由于类成员访问表达式b->b
。如果此表达式不是UB,则您的代码也不会是UB。
在[expr.ref]/1中,指定类成员访问构成对对象b
的访问(在->的左侧):
后缀表达式,后跟一个点。或一个箭头->(可选),后跟关键字模板([temp.names]),再后跟id表达式,是后缀表达式。 对点或箭头之前的后缀表达式进行求值; [67] 该求值的结果与id表达式一起确定整个后缀表达式的结果。
[67]如果评估了类成员访问表达式,则即使不需要确定整个后缀表达式的值(例如,如果id-expression表示静态成员),也将进行子表达式评估。
粗体
因此b->b
使用类型为a
的表达式读取对象B
的值,并且您引用的规则在此处适用。
答案 2 :(得分:0)
关于类似的类型,reinterpret_cast
部分提供了一些有用的解释和示例:
非正式地,如果忽略顶级cv资格,则两种类型相似:
- 它们是同一类型;或
- 它们都是指针,指向的类型相似;或
它们都是指向相同类的成员的指针,并且指向的成员的类型相似;或- 它们都是相同大小的数组,或者都是边界未知的数组,并且数组元素类型相似。
例如:
const int * volatile *
和int * *
const相似;const int (* volatile S::* const)[20]
和int (* const S::* volatile)[20]
相似;int (* const *)(int *)
和int (* volatile *)(int *)
相似;int (S::*)() const
和int (S::*)()
不相似;int (*)(int *)
和int (*)(const int *)
不相似;const int (*)(int *)
和int (*)(int *)
不相似;int (*)(int * const)
和int (*)(int *)
相似(它们是相同的类型);std::pair<int, int>
和std::pair<const int, int>
不相似。此规则启用基于类型的别名分析,在该分析中,编译器假定通过写入另一类型的glvalue不会修改通过一种类型的glvalue读取的值(受上述异常的影响)。 / p>
请注意,作为非标准语言扩展,许多C ++编译器都放宽了此规则,以允许通过联合的不活动成员进行错误类型的访问(这种访问在C中并非未定义