您可以通过 char* 访问任何对象的对象表示吗?
Can you access the object representation of any object through a char*?
我偶然发现了一个 reddit thread,用户在其中发现了 C++ 标准的有趣细节。该线程没有产生太多建设性的讨论,因此我将在这里重述我对问题的理解:
- OP 希望以符合标准的方式重新实现
memcpy
- 他们试图通过使用
reinterpret_cast<char*>(&foo)
来做到这一点,这是严格别名限制允许的例外,其中允许重新解释为 char
以访问对象的“对象表示” .
- [expr.reinterpret.cast] 说这样做会导致
static_cast<cv T*>(static_cast<cv void*>(v))
,所以 reinterpret_cast
在这种情况下相当于 static_cast 首先到 void *
然后至 char *
.
- [expr.static.cast] in combination with [basic.compound]
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. [...] [emphasis mine]
现在考虑以下联合 class:
union Foo{
char c;
int i;
};
// the OP has used union, but iiuc,
// it can also be a struct for the problem to arise.
OP 因此得出结论,在这种情况下将 Foo*
重新解释为 char*
会产生指向联合的第一个 char 成员(或其对象表示)的指针,而不是到联合本身的对象表示,即它仅指向 成员 。虽然这在表面上看起来是相同的,并且对应于相同的内存地址,但标准似乎区分了指针的“值”及其对应的地址,因为在抽象 C++ 机器上,指针属于某个对象只要。将它递增到该对象之外(与数组的 end() 相比)是未定义的行为。
OP 因此认为,如果标准强制 char*
与对象的第一个成员而不是整个联合对象的对象表示相关联,则在一次递增后取消引用它是 UB,这允许编译器进行优化,就好像结果 char*
不可能访问 int 成员的后续字节一样。这意味着不可能合法地访问 class 对象的完整对象表示,该对象可与 char
成员进行指针互换。
如果我理解正确的话,如果“union”被简单地替换为“struct”,同样适用,但我从原始线程中获取了这个例子。
你怎么看?这是标准缺陷吗?是误读吗?
This video,@KonradRudolph 在评论(现在聊天)中链接可能是问题的答案。
在 40 分钟左右,ISO C++ 委员会成员 Timur Doumler 讨论了访问字节表示的可能性。总结是,除 memcpy
之外的任何访问字节表示的尝试都是 UB。如果不使用 UB,OP 中的情况甚至不会出现,因为使用指向对象(如数组)或对其进行任何指针运算的行为本身就是 UB,因为这些操作仅 well-defined 当就抽象机而言,处理实际的数组对象。
此外,虽然将指针重新解释为 char*
本身并不违反别名规则,但从技术上讲并不能保证生成的 char*
将指向对象的第一个字节。
访问字节表示的唯一合法方法是 memcpy
将对象转换为 char 数组。这意味着重新实现 memcpy
是不可能的。
Timur Doumler 还将此描述为一个有望在 C++23 中修复的措辞缺陷,并提交了一篇论文,提出了对此的修复建议。
我偶然发现了一个 reddit thread,用户在其中发现了 C++ 标准的有趣细节。该线程没有产生太多建设性的讨论,因此我将在这里重述我对问题的理解:
- OP 希望以符合标准的方式重新实现
memcpy
- 他们试图通过使用
reinterpret_cast<char*>(&foo)
来做到这一点,这是严格别名限制允许的例外,其中允许重新解释为char
以访问对象的“对象表示” . - [expr.reinterpret.cast] 说这样做会导致
static_cast<cv T*>(static_cast<cv void*>(v))
,所以reinterpret_cast
在这种情况下相当于 static_cast 首先到void *
然后至char *
. - [expr.static.cast] in combination with [basic.compound]
A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. [...] [emphasis mine]
现在考虑以下联合 class:
union Foo{
char c;
int i;
};
// the OP has used union, but iiuc,
// it can also be a struct for the problem to arise.
OP 因此得出结论,在这种情况下将 Foo*
重新解释为 char*
会产生指向联合的第一个 char 成员(或其对象表示)的指针,而不是到联合本身的对象表示,即它仅指向 成员 。虽然这在表面上看起来是相同的,并且对应于相同的内存地址,但标准似乎区分了指针的“值”及其对应的地址,因为在抽象 C++ 机器上,指针属于某个对象只要。将它递增到该对象之外(与数组的 end() 相比)是未定义的行为。
OP 因此认为,如果标准强制 char*
与对象的第一个成员而不是整个联合对象的对象表示相关联,则在一次递增后取消引用它是 UB,这允许编译器进行优化,就好像结果 char*
不可能访问 int 成员的后续字节一样。这意味着不可能合法地访问 class 对象的完整对象表示,该对象可与 char
成员进行指针互换。
如果我理解正确的话,如果“union”被简单地替换为“struct”,同样适用,但我从原始线程中获取了这个例子。
你怎么看?这是标准缺陷吗?是误读吗?
This video,@KonradRudolph 在评论(现在聊天)中链接可能是问题的答案。
在 40 分钟左右,ISO C++ 委员会成员 Timur Doumler 讨论了访问字节表示的可能性。总结是,除 memcpy
之外的任何访问字节表示的尝试都是 UB。如果不使用 UB,OP 中的情况甚至不会出现,因为使用指向对象(如数组)或对其进行任何指针运算的行为本身就是 UB,因为这些操作仅 well-defined 当就抽象机而言,处理实际的数组对象。
此外,虽然将指针重新解释为 char*
本身并不违反别名规则,但从技术上讲并不能保证生成的 char*
将指向对象的第一个字节。
访问字节表示的唯一合法方法是 memcpy
将对象转换为 char 数组。这意味着重新实现 memcpy
是不可能的。
Timur Doumler 还将此描述为一个有望在 C++23 中修复的措辞缺陷,并提交了一篇论文,提出了对此的修复建议。