reinterpret_cast类型的修剪实际上是未定义的行为吗?

时间:2019-01-01 13:02:36

标签: c++ casting language-lawyer reinterpret-cast type-punning

似乎普遍认为,以某种方式禁止通过reinterpret_cast进行类型修饰(正确的是:“未定义行为”,即“ behavior for which this International Standard imposes no requirements”,并明确指出实现可以定义行为)。我使用以下理由不同意我是不正确的吗?,如果是,为什么


[expr.reinterpret.cast]/11状态:

  

如果类型“指向T1的指针”的表达式可以显式转换为类型“”,则可以将类型T2的glvalue表达式强制转换为类型“对T1的引用”。使用T2指向reinterpret_­cast”的指针。结果指向与源glvalue相同的对象,但是具有指定的类型。 [注意:也就是说,对于左值,引用强制转换reinterpret_­cast<T&>(x)与具有内置*reinterpret_­cast<T*>(&x)&运算符的转换*具有相同的效果(对于reinterpret_­cast<T&&>(x))。 —结束注释]不会创建临时文件,不会创建副本,也不会调用构造函数或转换函数。

带有脚注:

  

75)有时也称为类型pun

例如,

/ 11隐含了/ 6到/ 10的限制,但是也许最常见的用法(与 objects 关联)在[expr.reinterpret.cast]/7中得到了解决:

  

可以将对象指针显式转换为其他类型的对象指针。将对象指针类型的prvalue v转换为对象指针类型“ cv T的指针”时,结果为static_­cast<cv T*>(static_­cast<cv void*>(v))。 [注意:将类型为“指向T1的指针”的prvalue转换为“指向T2的指针的类型(其中T1 and T2是对象类型,并且T2的对齐要求不比T1严格,返回其原始类型将产生原始指针值。 —尾注]

很明显,目的不能转换为指针或对void的引用,如:

  1. / 7中的示例清楚地表明,static_cast就象指针一样足够了,[expr.static.cast]/13[conv.ptr]/2也是如此;和
  2. void的引用转换为prima facie invalid

此外,[basic.lval]/8指出:

  

如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

     

(8.1)对象的动态类型,

     

(8.2)对象的动态类型的cv限定版本,

     

(8.3)与对象的动态类型相似的类型,

     

(8.4)一种类型,它是与对象的动态类型相对应的有符号或无符号类型,

     

(8.5)一种类型,它是与对象的动态类型的cv限定版本相对应的有符号或无符号类型,

     

(8.6)聚合或联合类型,其元素或非静态数据成员(包括递归地包括子聚合或所包含的联合的元素或非静态数据成员)中包括上述类型之一, >      

(8.7)一种类型,它是对象的动态类型的(可能是cv限定的)基类类型,

     

(8.8)一个char,unsigned char或std :: byte类型。

如果我们暂时回到[expr.reinterpret.cast]/11,我们会看到“结果引用同一对象作为源glvalue,但具有指定的类型 >。”这对我来说是一条明确的声明,即reinterpret_cast<T&>(v)的结果是对类型T 的对象的左值引用,访问显然是“通过glvalue的” “对象的动态类型”。这句话还提出了这样一个论点,即[basic.life]的各个段落是通过虚假的主张而适用的,即这样的转换的结果引用了类型为T的新对象,该对象的生存期尚未开始,因此< em>恰好发生v驻留在相同的内存地址。

仅仅为了禁止结果的标准定义使用而明确定义此类转换似乎是没有意义的,尤其是鉴于脚注75指出这种[参考]转换是“有时称为类型pun 。”

请注意,我的引用是针对C ++ 17(N4659)的最终公开草案,但所讨论的语言从N3337 (C++11)N4788 (C++20 WD)几乎没有变化(提示链接可能会请稍后再参考)。实际上,[expr.reinterpret.cast] / 11的the footnote在最新的草案中更加明确:

  

当结果指向与源glvalue相同的对象时,有时将其称为类型pun

3 个答案:

答案 0 :(得分:7)

我相信您的误解就在这里:

  

这对我来说是一条明确的声明,即reinterpret_cast<T&>(v)的结果是对类型为T 的对象的左值引用,显然可以通过“ “对象的动态类型”的glvalue。

[basic.lval]/8有点误导,因为当动态类型实际上是用于访问对象而不是对象本身的glvalue [defns.dynamic.type]的属性时,它谈论的是“对象”的动态类型。 。本质上,glvalue的动态类型是is currently living在glvalue所指位置的对象的类型(实际上,是在该内存中构造/初始化的对象的类型){{3 }}。例如:

float my_float = 42.0f;
std::uint32_t& ui = reinterpret_cast<std::uint32_t&>(my_float);

此处,ui是引用,它引用由my_float的定义创建的对象。通过glvalue ui访问该对象将调用未定义的行为(每个[intro.object]/6),因为glvalue的<动态>动态类型float,而 glvalue的>类型std::uint32_t

像这样的reinterpret_cast很少有有效的用途,但是除了仅强制转换为void*和返回之外,还存在用例(对于后者,static_cast就足够了,注意自己)。 [basic.lval]/8.1有效地为您提供了它们的完整列表。例如,通过将对象的地址强制转换为对象的值来检查(甚至复制,如果对象的动态类型是平凡可复制的 [basic.lval]/8)也是有效的。 char*unsigned char*std::byte8(但不是signed char*)。 reinterpret_cast有符号类型的对象以其对应的无符号类型访问它是有效的,反之亦然。将指向联合的指针/引用转换为该联合的成员的指针/引用,并通过结果左值 访问该成员也是有效的,即该成员是联合的活动成员…

通常无法定义通过此类强制类型转换的类型的主要原因是,使其定义为行为将禁止某些极其重要的编译器优化。如果您允许通过任何其他类型的左值简单地访问任何类型的任何对象,则编译器将必须假定通过某个左值对对象的任何修改都可能会影响程序中任何对象的值,除非事实可以证明。结果,例如,基本上不可能将寄存器中的内容保留任何有用的时间,因为对任何内容的任何修改都会立即使您当前在寄存器中的内容无效。是的,任何优秀的优化程序都将执行[basic.types]/9。但是,尽管这样的方法确实有效并且功能强大,但从原则上讲它们只能涵盖部分情况。通常,基本上无法证明或证明混叠(相当于解决我认为的暂停问题)……

答案 1 :(得分:2)

[basic.lval] / 8表示何时将肯定不确定该行为,但这并不一定意味着如果您从[basic.lval] / 8中的列表中进行操作,则该行为将被定义。

[basic.lval] / 8自C ++ 98起没有太大变化,并且其措词不准确,例如使用了未定义的术语“对象的动态类型”。 (C ++为表达式定义了动态类型。)

如果您执行[basic.lval] / 8允许的操作,则行为定义取决于标准的其他部分。即使可以同意可以从[basic.types]中的措词中得出有符号/无符号重新解释的结果,我也无法想象如何预测对包含引用或虚拟方法的对象的访问结果通过char glvalue。

C ++ 17的新指针和glvalue强制转换规则使[basic.lval] / 8更加无用,因为现在无法正式实现[basic.lval] / 8旨在保证的目标(例如,通过char glvalue读取对象中的字节)。正如您所指出的那样,按照[expr.reinterpret.cast] / 7,在reinterpret_cast对T的引用之后,生成的glvalue仍然引用了reinterpret_cast参数所引用的对象。

对于每个[conv.lval]/(3.4),左值到右值转换的结果是转换后的glvalue所引用的对象中包含的值。例如,这些规则意味着应用于reinterpret_cast<char&>(i)(其中iint变量的i的左值到右值转换的结果是存储在{{1} } int对象。 prvalue的类型为char[conv.lval]/1),如果i不能表示char的值,则根据[expr]/1,行为是不确定的。即使{basic.lval“允许”访问,即使int无法表示对象的值,尝试通过char glvalue读取char对象也会导致UB。 ] /(8.8)。这证明了第一段中所说的话。

答案 2 :(得分:0)

如果中间类型具有相等或更严格的对齐要求,则可以使用reinterpret_cast构建的引用(包括转换指针然后取消引用)可以往返到原始类型。

由于严格的别名规则,大多数其他用途是未定义的行为。 (不需要语言引用,因为问题已经将其引用了)

值得注意的法律案例中,表达式最终类型与对象的动态类型不匹配,包括通过窄字符类型的别名以及结构的常见初始序列规则。