通过 unsigned char 别名访问对象,加载和存储时会发生什么?
Object access through an unsigned char alias, what happens on load and on store?
在下面的示例中,数组不是通过其第一个元素访问的,而是通过概念上的数组别名访问的。然而,根据 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?
}
根据these 标准,数组是对象,可以通过unsigned char
指针和引用来检查对象。让我们分解你的代码。
首先,我们声明一个 unsigned char
大小为 1 的数组。
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 *
以检查其表示形式。
accessing is ok, but what can be asserted about the value? And for
exemple I make a store through the object name, then a store through
the aliasing pointer and then a load through the object name, is there
no risk the compiler optimize away the last load and reflect it by an
immediate which would equal the first store?
访问在[defns.access]中定义为:
read or modify the value of an object
因此通过 *alias_to_array_s='Y';
修改值与阅读它一样可以接受。
允许编译器通过 as-if 规则优化 load/stores。您的程序没有任何可观察到的行为。如果断言通过,编译器可以自由地将 g()
替换为空主体,并且根本不调用它。如果您真的担心编译器会重新排序 load/stores,您应该使用 volatile
或查看内存障碍。
在下面的示例中,数组不是通过其第一个元素访问的,而是通过概念上的数组别名访问的。然而,根据 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?
}
根据these 标准,数组是对象,可以通过unsigned char
指针和引用来检查对象。让我们分解你的代码。
首先,我们声明一个 unsigned char
大小为 1 的数组。
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 *
以检查其表示形式。
accessing is ok, but what can be asserted about the value? And for exemple I make a store through the object name, then a store through the aliasing pointer and then a load through the object name, is there no risk the compiler optimize away the last load and reflect it by an immediate which would equal the first store?
访问在[defns.access]中定义为:
read or modify the value of an object
因此通过 *alias_to_array_s='Y';
修改值与阅读它一样可以接受。
允许编译器通过 as-if 规则优化 load/stores。您的程序没有任何可观察到的行为。如果断言通过,编译器可以自由地将 g()
替换为空主体,并且根本不调用它。如果您真的担心编译器会重新排序 load/stores,您应该使用 volatile
或查看内存障碍。