问题描述
似乎普遍认为通过某种方式禁止通过reinterpret_cast
进行类型修饰(正确地:未定义的行为",即"对此国际标准不施加任何要求的行为",并明确指出实现可以定义C ++中的行为.我使用以下理由不同意我是不正确的吗?,为什么??
It appears to be widely-held that type punning via reinterpret_cast
is somehow prohibited (properly: "undefined behavior", that is, "behavior for which this International Standard imposes no requirements", with an explicit note that implementations may define behavior) in C++. Am I incorrect in using the following reasoning to disagree, and if so, why?
[expr.reinterpret.cast]/11 状态:
带有脚注:
例如,
/11隐含了/6到/10的限制,但 [expr.reinterpret.cast]/7 :
很明显,目的不能转换为指针或对void
的引用,如:
Clearly the purpose cannot be conversion to/from pointers or references to void
, as:
- /7中的示例清楚地表明,
static_cast
应该足以满足指针要求,就像 [expr.static.cast]/13 和 [conv.ptr]/2 ;和 - 对[c19>的引用转换为] 基本外观无效.
- the example in /7 clearly demonstrates that
static_cast
should suffice in the case of pointers, as do [expr.static.cast]/13 and [conv.ptr]/2; and - [conversions to] references to
void
are prima facie invalid.
此外, [basic.lval]/8 状态:
(8.1)对象的动态类型,
(8.1) the dynamic type of the object,
(8.2)对象的动态类型的cv限定版本,
(8.2) a cv-qualified version of the dynamic type of the object,
(8.3)与对象的动态类型相似的类型,
(8.3) a type similar to the dynamic type of the object,
(8.4)一种类型,它是与对象的动态类型相对应的有符号或无符号类型,
(8.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,
(8.5)一种类型,它是与对象的动态类型的cv限定版本相对应的有符号或无符号类型,
(8.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
(8.6)集合或联合类型,在其元素或非静态数据成员(包括递归地包括子集合或包含的联合的元素或非静态数据成员)中包括上述类型之一,
(8.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
(8.7)一种类型,它是对象的动态类型的(可能是cv限定的)基类类型,
(8.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
(8.8)一个char,unsigned char或std :: byte类型.
(8.8) a char, unsigned char, or std::byte type.
如果我们返回到 [expr.reinterpret.cast]/11 一会儿,我们看到结果指向与源glvalue 相同的对象,但具有指定的类型".这对我来说是一条明确的陈述,即reinterpret_cast<T&>(v)
的结果是对类型为T
的对象的左值引用,访问该对象显然是通过动态类型的glvalue"对象".这句话还解决了这样一个论点,即 [basic.life] 的各个段落通过虚假声明适用,即此类转换的结果引用了类型为T
的新对象,该对象的生存期尚未开始,恰好发生与v
.
And if we return to [expr.reinterpret.cast]/11 for a moment, we see "The result refers to the same object as the source glvalue, but with the specified type." This reads to me as an explicit statement that the result of reinterpret_cast<T&>(v)
is an lvalue reference to an object of type T
, to which access is clearly "through a glvalue of" "the dynamic type of the object". This sentence also addresses the argument that various paragraphs of [basic.life] apply via the spurious claim that the results of such conversions refer to a new object of type T
, the lifetime of which has not yet begun, which just happens to reside at the same memory address as v
.
仅在不允许结果的标准定义使用 时明确定义此类转换似乎是没有意义的,尤其是根据脚注75指出,这种[reference]转换是有时称为类型pun ."
It seems nonsensical to explicitly define such conversions only to disallow standard-defined use of the results, particularly in light of footnote 75 noting that such [reference] conversion is "sometimes referred to as a type pun."
请注意,我的引用是针对C ++ 17(N4659)的最终公开发布的草案,但是所讨论的语言与 N3337(C ++ 11)通过 N4788(C ++ 20 WD)(提示链接可能会及时引用以后的草稿).实际上脚注到[expr.reinterpret.cast ]/11在最新草案中变得更加明确:
Note that my references are to the final publicly-available draft for C++17 (N4659), but the language in question is little-changed from N3337 (C++11) through N4788 (C++20 WD) (tip link will likely refer to later drafts in time). In fact the footnote to [expr.reinterpret.cast]/11 is made even more explicit in the most recent draft:
推荐答案
我相信您的误解就在这里:
I believe your misunderstanding lies here:
[basic.lval]/8 是一个有点误导,因为当动态类型实际上是glvalue的属性时,它谈论的是对象"的动态类型 [defns.dynamic.type] 用于访问对象,而不是对象本身.本质上,glvalue的动态类型是当前处于活动状态的对象的类型. 在glvalue所指的位置(有效地是在该内存中构造/初始化的对象的类型) [对象对象]/6 .例如:
[basic.lval]/8 is a bit misleading because it talks about the dynamic type "of the object" when the dynamic type is actually a property of the glvalue [defns.dynamic.type] used to access the object rather than the object itself. Essentially, the dynamic type of the glvalue is the type of the object that is currently living in the place that the glvalue refers to (effectively, the type of the object that was constructed/initialized in that piece of memory) [intro.object]/6. For example:
float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);
在这里,ui
是引用,引用由my_float
的定义创建的对象.通过glvalue ui
访问此对象将调用未定义的行为(每个 [ basic.lval]/8.1 ),因为glvalue的动态类型为float
,而glvalue的 type 为std::uint32_t
.
here, ui
is a reference that refers to the object created by the definition of my_float
. Accessing this object through the glvalue ui
would invoke undefined behavior (per [basic.lval]/8.1), however, because the dynamic type of the glvalue is float
while the type of the glvalue is std::uint32_t
.
像这样的reinterpret_cast
几乎没有有效的用途,但是除了强制转换为void*
并返回之外,还存在用例(对于后者,如您所指出的,static_cast
就足够了). [basic.lval]/8 有效地为您提供了一个完整的他们是什么的清单.例如,检查(甚至复制对象的动态类型是否是简单可复制 [basic.types]/9 ),通过将对象的地址强制转换为char*
,unsigned char*
或std::byte8
(而不是signed char*
). reinterpret_cast
一个带符号类型的对象将其作为其对应的无符号类型进行访问将是有效的,反之亦然.将一个指针/对联合的引用转换为对该联合的成员的指针/引用并通过结果左值访问该成员也是有效的,如果该成员是union&hellip的活动成员;
There are few valid uses of a reinterpret_cast
like that, but use cases other than just casting to void*
and back exist (for the latter, static_cast
would be sufficient, as you noted yourself). [basic.lval]/8 effectively gives you a complete list of what they are. For example, it would be valid to examine (and even copy if the dynamic type of the object is trivially-copyable [basic.types]/9) the value of an object by casting the address of the object to char*
, unsigned char*
, or std::byte8
(not signed char*
, however). It would be valid to reinterpret_cast
an object of signed type to access it as its corresponding unsigned type and vice versa. It would also be valid to cast a pointer/reference to a union to a pointer/reference to a member of that union and access that member through the resulting lvalue if that member is the active member of the union…
通常无法定义通过此类强制类型转换的类型的主要原因是,使其定义为行为将禁止某些极其重要的编译器优化.如果您允许通过任何其他类型的左值简单地访问任何类型的任何对象,则编译器将必须假定通过某个左值对对象的任何修改都可能会影响程序中任何对象的值,除非可以证明是相反的.结果,例如,基本上不可能将寄存器中的内容保留任何有用的时间,因为对任何内容的任何修改都会立即使您此时在寄存器中拥有的任何内容失效.是的,任何优秀的优化程序都会执行别名分析.但是,尽管这样的方法确实有效并且功能强大,但从原则上讲,它们只能涵盖部分情况.通常,基本上无法证明或证明混叠(相当于解决我认为的停顿问题)…
The main reason why type punning through casts like this is undefined in general is that making it defined behavior would prohibit some extremely vital compiler optimizations. If you'd allow any object of any type to simply be accessed through an lvalue of any other type, then the compiler would have to assume that any modification of an object through some lvalue can potentially affect the value of any object in the program unless it can prove otherwise. As a result, it would basically be impossible, for example, to keep stuff around in registers for any useful period of time because any modification of anything would immediately invalidate whatever you may have in registers at the moment. Yes, any good optimizer will perform aliasing analysis. But, while such methods certainly work and are powerful, they can, out of principle, only cover a subset of cases. Disproving or proving aliasing in general is basically impossible (equivalent to solving the halting problem I would think)…
这篇关于reinterpret_cast类型的punning是否实际上是未定义的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!