【问题标题】:Implementation of addressofaddressof的实现
【发布时间】:2017-10-24 18:48:37
【问题描述】:

以前不知道std::addressof 的存在,它存在的原因对我来说是有意义的:作为在存在重载operator& 的情况下获取地址的一种方式。然而,实现稍微不透明。来自gcc 4.7.1

template<typename _Tp>
inline _Tp*
__addressof(_Tp& __r) _GLIBCXX_NOEXCEPT
{
  return reinterpret_cast<_Tp*>
(&const_cast<char&>(reinterpret_cast<const volatile char&>(__r)));
}

reinterpret_cast&lt;_Tp*&gt; 很明显。剩下的就是黑魔法了。有人可以分解这实际上是如何工作的吗?

【问题讨论】:

  • 强制转换为char&amp;(或char *)可以避免破坏严格的别名规则,并且operator&amp; 不能为char 等内置类型重载。所以你最终得到了一个指向_Tp基地址的指针。
  • @Praetorian 别名规则在这里不是问题,因为您不会取消引用结果。之所以使用char,是因为它不能有对齐要求;如果你使用int,并且int的对齐比_Tp更严格,转换可能会改变实际地址。
  • @JamesKanze Ahh,当然,没有考虑过不阅读演员表的结果。幸好我发表了评论而不是答案:)
  • @syam 使用转换结果(除非它是字符类型)是未定义的行为,因此实现可以做任何它想做的事情。在字节寻址的机器上,reinterpret_cast 转换通常在机器代码级别不执行任何操作。然而,在 word 寻址的机器上,int* 通常会小于char*char*int* 的转换会丢失数据。
  • @syam re "anything it want": 有一个要求,如果一个地址被转换成一个没有更严格对齐要求的类型,然后再回到原来的类型,结果指针将与原始指针进行比较。但就标准要求而言,就是这样。

标签: c++ c++11


【解决方案1】:
  • 首先你有__r,它的类型是_Tp&amp;
  • reinterpret_cast'ed 到char&amp; 以确保以后能够获取其地址而不必担心原始类型中的operator&amp; 过载;实际上它被强制转换为const volatile char&amp;,因为reinterpret_cast 总是可以合法地添加constvolatile 限定符,即使它们不存在,但如果它们存在,它不能删除它们(这确保了任何限定符@987654330 @ 最初,他们不干扰演员表)。
  • 这是 const_cast'ed 到 char&amp;,删除了限定符(现在合法!const_cast 可以做 reinterpret_cast 在限定符方面做不到的事情)。
  • 地址是&amp;(现在我们有一个普通的char*
  • reinterpret_cast 编回 _Tp*(包括原始的 constvolatile 限定符,如果有的话)。

编辑:由于我的回答已被接受,我将彻底补充并补充说,选择char 作为中间类型是由于对齐问题,以避免触发未定义的行为。有关完整说明,请参阅@JamesKanze 的 cmets(在问题下)。感谢 James 解释得这么清楚。

【讨论】:

  • 谢谢。有了这个以及其他帖子中提出的对齐事实,现在一切都变得更有意义了。
  • 确认 - 在reinterpret_cast 期间不能应用任何转换运算符,对吗?它们只能在static_cast? 期间应用
  • @templatetypedef:是的。 static_cast、dynamic_cast 和传统 C 转换实际上在需要时执行转换,而 reinterpret_cast 和 const_cast 仅更改编译器认为数据的类型,而不会转换数据本身。
【解决方案2】:

简短版:

operator&amp; 不能为 char 重载。因此,该类型被强制转换为 char 引用以获取保证为真实地址的地址。

由于const_castreinterpret_cast 的限制,这种转换分两次进行。

更长的版本:

它正在执行三个连续的强制转换。

reinterpret_cast<const volatile char&>

这实际上是转换为char&amp;constvolatile 之所以存在,是因为 _Tp 可能是 constvolatile,而 reinterpret_cast 可以添加这些,但无法删除 em> 他们。

const_cast<char&>

现在constvolatile 已被删除。 const_cast 可能会这样做。

reinterpret_cast<_Tp*>(&result)

现在获取地址并将类型转换回指向原始类型的指针。

【讨论】:

    【解决方案3】:

    由内而外:

    • 首先,它将__r 类型转换为const volatile char&amp;:它转换为char&amp;,只是因为它肯定没有重载的operator&amp;,它会做一些时髦的事情。 const volatile 在那里,因为这些是限制,它们可以被添加但​​不能被 reinterpret_cast 删除。 _Tp 可能已经是const 和/或volatile,在这种情况下,此演员阵容中需要一个或两个。如果没有,演员只是不必要地添加了它们,但它是为最严格的演员而写的。

    • 接下来,要删除const volatile,您需要一个const_cast,这会导致下一部分...const_cast&lt;char&amp;&gt;

    • 他们只需从那里获取地址并将其转换为您想要的类型,_Tp*。请注意,_Tp 可能是const 和/或volatile,这意味着此时可以添加回这些内容。

    【讨论】:

      【解决方案4】:

      仔细想想其实很简单,要在重载的operator&amp; 之前获得对象/函数的真实地址,您需要将对象视为不同于其真实存在的东西,某种类型不能有重载的操作符.. 内在类型(例如char)。

      char 没有对齐,可以驻留在任何其他对象可以驻留的任何位置,话虽如此;将对象转换为对 char 的引用是一个很好的开始。


      但是在执行reinterpret_cast&lt;const volatile char&amp;&gt; 时所涉及的黑魔法呢?

      为了重新解释addressof 的实现返回的指针,我们最终将要丢弃诸如constvolatile 之类的限定符(最终得到一个简单的引用@987654328 @)。这两个可以通过reinterpret_cast 轻松添加,但要求删除它们是非法的。

      T1 const a; reinterpret_cast<T2&> (a);
      
      /* error: reinterpret_cast from type ‘...’ to type ‘...’ casts away qualifiers */
      

      这有点“比抱歉更安全”技巧.. “让我们添加它们,以防万一,我们稍后会删除它们。” em>


      后来我们用const_cast&lt;char&amp;&gt; 抛弃了限定符(constvolatile),最终得到一个对char 的简单引用,这个结果是,因为最后一步,变回指向我们传递给实现的任何类型的指针。

      这个阶段的一个相关问题是,为什么我们没有跳过reinterpret_cast的使用,而是直接去了const_cast?这也有一个简单的答案:const_cast 可以添加/删除限定符,但不能更改底层类型。

      T1 a; const_cast<T2&> (a);
      
      /* error: invalid const_cast from type ‘T1*’ to type ‘T2*’ */
      

      它可能不像馅饼那么容易,但是当你得到它时它的味道肯定很好..

      【讨论】:

        猜你喜欢
        • 2015-10-19
        • 1970-01-01
        • 1970-01-01
        • 2011-10-30
        • 1970-01-01
        • 2014-12-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多