在下面的示例中,数组不是通过其第一个元素访问的,而是通过概念上是数组的别名访问的。然而,根据 C++17/[basic.lval]/8,可以通过无符号字符访问对象的存储值。那么,认为以下断言永远不会成立是正确的吗?
void g(){
unsigned char s[]={'X'};
unsigned char (*pointer_to_array_s)[1] = &s;
unsigned char *alias_to_array_s =
reinterpret_cast<unsigned char*>(pointer_to_array_s);
//alias_to_array_s is not a pointer whose value point to c[0] because an array
//and its firts element are not pointer interconvertible see [basic.compound]
//*alias_to_array_s aliases s;
assert(*alias_to_array_s=='X'); //may fire?
}
事实上,alias_to_array_s
不是指向s
第一个元素的有效指针是由于C++17中引入的微妙之处,请参阅此问答。
现在假设我通过别名修改了数组,我可以通过直接访问数组来检索这个修改吗?
void g(){
unsigned char s[]={'X'};
unsigned char (*pointer_to_array_s)[1] = &s;
unsigned char *alias_to_array_s =
reinterpret_cast<unsigned char*>(pointer_to_array_s);
*alias_to_array_s='Y'; //UB?
assert(s[0]=='Y');//may fire?
}
访问是可以的,但是关于该值可以断言什么?而对于 例如,我通过对象名称创建一个存储,然后通过 锯齿指针,然后通过对象名称加载,在那里 没有风险,编译器优化了最后一个负载,并通过 立即哪个等于第一家商店?
访问在 [defns.access] 中定义为:
读取或修改对象的值
因此,通过*alias_to_array_s='Y';
修改值与读取它一样可以接受。
允许编译器通过 as-if 规则优化加载/存储。程序没有任何可观察的行为。如果断言通过,编译器可以自由地将g()
替换为空主体,并且根本不调用它。如果您真的担心编译器对加载/存储重新排序,您应该使用volatile
或查看内存障碍。
根据这些标准,数组是对象,对象可以通过unsigned char
指针和引用进行检查。让我们分解一下您的代码。
首先,我们声明一个大小为 1 的unsigned char
数组。
unsigned char s[]={'X'};
我们创建一个指向unsigned char[1]
的指针。这与s
的类型相同,所以我们很好。
unsigned char (*pointer_to_array_s)[1] = &s;
现在这是棘手的部分。您的评论暗示下一次转换将使alias_to_array_s
指向s
的第一个成员,这可能是 UB。然而,s
是一个对象,它的指针可以reinterpret_cast
到char
、unsigned char
或std::byte
,以检查它的表示。因此下一行被很好地定义为创建一个指向s
表示的第一个字节的指针。
unsigned char *alias_to_array_s =
reinterpret_cast<unsigned char*>(pointer_to_array_s);
通过alias_to_array_s
修改s
的另一个示例也应该没问题,因为您正在修改对象表示形式的第一个字节。在unsigned char[]
的情况下,这是第一个元素。这里重要的一点是,您没有将指针到数组转换为指向第一个元素的指针。将指向数组的指针转换为unsigned char *
以检查其表示形式。