代码之家  ›  专栏  ›  技术社区  ›  sinek

通过void*进行铸造,而不是使用reinterpret_cast

  •  37
  • sinek  · 技术社区  · 15 年前

    我在读一本书,我发现 reinterpret_cast 不应直接使用,而应将铸件与 static_cast :

    T1 * p1=...
    void *pv=p1;
    T2 * p2= static_cast<T2*>(pv);
    

    而不是:

    T1 * p1=...
    T2 * p2= reinterpret_cast<T2*>(p1);
    

    然而,我找不到一个解释,为什么这比直接演员更好。如果有人能给我解释或指点我答案,我将不胜感激。

    提前谢谢

    P.S.我知道什么是 重新解释铸模 用过,但我从没见过这样用过

    3 回复  |  直到 7 年前
        1
  •  26
  •   Pavel Minaev    15 年前

    对于允许此类铸造的类型(例如,如果 T1 是一种吊舱类型,并且 T2 unsigned char )方法 static_cast 由标准明确定义。

    另一方面, reinterpret_cast 是完全实现定义的-唯一的保证是,您可以将指针类型强制转换为任何其他指针类型,然后返回,然后获得原始值;此外,您还可以将指针类型强制转换为足够大的整型,以容纳指针值(根据实现的不同而不同,并且不需要存在于所有的),然后再把它扔回去,你就会得到原始值。

    更具体地说,我只会引用标准的相关部分,重点强调以下重要部分:

    5.2.10【expr.reinterpret.cast】:

    reinterpret_cast执行的映射是 定义的实现 . [注:它可能,也可能不产生与原始值不同的表示。]。指向对象的指针可以显式转换为指向不同类型对象的指针。)但将类型为__的右值转换为类型为__的指针转换为类型为__的指针转换为类型为T2__的指针(其中T1和T2是对象类型,并且T2的对齐要求不比T1的更严格),然后返回到其原始类型将生成原始指针值, 这种指针转换的结果未指明。 .

    所以像这样:

    struct pod_t { int x; };
    pod_t pod;
    char* p = reinterpret_cast<char*>(&pod);
    memset(p, 0, sizeof pod);
    

    实际上未指明。

    解释原因 静态铸造 工作有点棘手。这是上面重写的代码 静态铸造 我相信,该标准保证始终按预期工作:

    struct pod_t { int x; };
    pod_t pod;
    char* p = static_cast<char*>(static_cast<void*>(&pod));
    memset(p, 0, sizeof pod);
    

    再一次,让我引用标准的章节,这些章节共同引导我得出结论,上述内容应该是可移植的:

    3.9[基本类型]:

    对于pod类型t的任何对象(基类子对象除外),无论该对象是否持有类型t的有效值,构成该对象的基础字节(1.7)都可以复制到char或无符号char数组中。如果char或unsigned char数组的内容被复制回对象中,则对象随后应保留其原始值。

    t型对象的对象表示是n个无符号字符的序列。 物体 由t类型的对象占用,其中n等于sizeof(t)。

    3.9.2【碱性化合物】:

    CV合格(3.9.3)或CV不合格类型的对象 void* (指向void的指针),可用于指向未知类型的对象。A 空洞* 应能容纳任何物体指针。 合格或不合格的简历(3.9.3) 空洞* 应具有与合格或不合格的CV相同的表示和对准要求。 char* .

    3.10[基本等级]:

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

    • 字符或无符号字符类型 .

    4.10〔VAN.PTR〕:

    _指向cv t的指针,_其中t是对象类型,可以转换为_指向cv void的指针的值。_将_指向cv t_指向cv void_的指针指向t类型对象所在的存储位置的起点,就好像该对象是最派生的对象一样。t类型的对象(1.8)(即,不是基类子对象)。

    5.2.9【膨胀静态铸造】:

    除左值到右值(4.1)、数组拓扑(4.2)、函数到指针(4.3)和布尔(4.12)转换外,任何标准转换序列(第4条)的倒数都可以使用静态转换显式执行。

    [编辑] 另一方面,我们有这个宝石:

    9.2[等级mem]/17:

    指向pod结构对象的指针,通过reinterpret-cast进行适当转换,指向其初始成员(或者如果该成员是位字段,则指向其所在的单元),反之亦然。[注:可能 因此 在pod结构对象内填充未命名的填充,但不要在其开始处填充,以实现适当的对齐。]

    这似乎意味着 重新解释铸模 指针之间意味着“相同的地址”。算了吧。

        2
  •  6
  •   curiousguy    13 年前

    毫无疑问,这两种形式都有很好的定义,但措辞并没有抓住这一点。

    这两种形式都将在实践中发挥作用。

    reinterpret_cast 更明确的意图,应优先考虑。

        3
  •  3
  •   Charles Eli Cheese    15 年前

    之所以如此,是因为C++如何定义继承,以及因为成员指针。

    对于C,指针基本上只是一个地址,它应该是。在C++中,由于它的一些特性,它必须更复杂。

    成员指针实际上是类中的偏移量,因此使用C样式强制转换它们总是一个灾难。

    如果您已经多次继承了两个也有一些具体部分的虚拟对象,这对于C样式来说也是一个灾难。但是,在多重继承中,这种情况会导致所有的问题,所以无论如何您都不应该使用它。

    真的希望你一开始就不要使用这些案例。另外,如果你投了很多,这是另一个迹象,你在你的设计混乱。

    我最后一次铸造是用C++中的原语决定的是不一样的,但是显然它们必须是。对于实际的对象,任何时候你想要投射一些东西,就开始质疑你的设计,因为大多数时候你应该“编程到接口”。当然,您不能更改第三方API的工作方式,因此您没有太多的选择。

    推荐文章