reinterpret_cast <char *>(p)或static_cast <char *>((void *)p))对于字节指针的区别,哪个更好?</char *> </char *>

时间:2014-09-03 07:36:52

标签: c++ pointers casting

以下三个强制转换用于提取用于指针算术的原始字节指针之间有什么区别吗? (假设char为1个字节的平台。)

  1. static_cast<char*>((void*)ptr))
  2. reinterpret_cast<char*>(ptr)
  3. (已更新)或:static_cast<char*>(static_cast<void*>(ptr))
  4. 我更喜欢哪一个?

    更详细......

    给定一个类中两个成员对象的指针,我想计算从一个到另一个的偏移量,这样我就可以重建给定偏移量的一个成员的地址和另一个成员的地址。

    // assumed data layout:
    struct C {
      // ...
      A a;
      // ...
      B b;
    }
    

    我目前使用的代码是:

    void approach1( A *pa, B *pb )
    {
      // compute offset:
      std::ptrdiff_t offset = static_cast<char*>((void*)pa) - static_cast<char*>((void*)pb);
      // then in some other function...
      // given offset and ptr to b, compute ptr to a:
      A *a = static_cast<A*>( (void*)(static_cast<char*>((void*)pb) + offset) );
    }
    
    main()
    {
      C c;
      approach1(&c.a, &c.b);
    }
    

    我想知道以下是否更好(或更糟):

    void approach2( A *pa, B *pb )
    {
      std::ptrdiff_t offset = reinterpret_cast<char*>(pa) - reinterpret_cast<char*>(pb);
      // ...
      A *a = reinterpret_cast<A*>( reinterpret_cast<char*>(pb) + offset );
    }
    

    两种方法完全相同吗?它们同样便携吗?

    我的印象是approach1()更具可移植性,因为“static_casting a pointer to and from void* preserves the address”,而reinterpret_cast<>保证较少(请参阅链接中接受的答案)。

    我想知道最干净的方法是什么。

    更新:目的说明

    很多人都问过计算这些补偿的目的是什么。目的是构造实例偏移的元类表。这由运行时反射机制用于自动GUI构建和持久性(偏移量不是序列化的,仅用于遍历结构)。该代码已经生产超过15年。出于这个问题的目的,我只想知道计算指针偏移的最便携方式。我无意对元类系统的工作方式进行大的改动。另外,我一般也对最好的方法感兴趣,因为我有其他用途(例如共享内存代码的差异指针)。

    注意:我无法使用offsetof()因为在我的实际代码中我只有指向实例ab的指针,我不一定具有包含对象c的类型或使用offsetof()的其他静态信息。我只能假设ab是同一个对象的成员。

2 个答案:

答案 0 :(得分:6)

这两个将导致相同的结果,因此差异主要是语义上的,reinterpret_cast具有您想要的操作的含义,以及只需要一个演员而不是两个演绎的事实(并且在你的代码中投射得更好)。

  

reinterpret_cast

     

5.2.10 / 7 :对象指针可以显式转换为不同类型的对象指针。当一个prvalue v   对象指针类型被转换为对象指针类型“指向cv T的指针”,结果是static_cast&lt; cv T *&gt;(static_cast&lt; cv void *&gt;(v))。

所以除非在中年平台上出现一个exotique随机低级别的不同行为,你一定要选择:

reinterpret_cast<char*>(ptr);

一般而言。

那就是说,为什么不在你的情况下使用uintptr_t?它更加合适,你不需要指针:

void approach3( A *pa, B *pb )
{
  std::ptrdiff_t offset = reinterpret_cast<std::uintptr_t>(pa) - reinterpret_cast<std::uintptr_t>(pb);
  // ...
  A *a = reinterpret_cast<A*>( reinterpret_cast<std::uintptr_t>(pb) + offset );
}

有关其他信息,请参阅:

http://en.cppreference.com/w/cpp/language/reinterpret_cast

答案 1 :(得分:0)

我不建议计算班级成员之间的偏移距离。地址。编译器可能会注入填充数据,或者即使它正在工作,它也只能在该特定主机上运行的特定编译器上以相同的方式工作。应用此实践时存在大量错误来源。例如,如果你必须处理着名的Virtual tables and memory layout in multiple virtual inheritance怎么办?这将完全使您的解决方案无法使用。

回到根源:你为什么要这样做?也许有更好的解决方案。

修改/更新

感谢您解释原因。这是一个我直到现在才看到的非常有趣的方法。我今天学到了一些东西。

但是,我仍然坚持认为应该有一种更容易处理的方法。就像一个证明的概念,我写了一个小应用程序,只是为了看看你的哪些方法正常工作。对我来说,他们都不工作。

应用程序是稍微扩展的一种方法,在这里:

#include <iostream>
#include <stdio.h>
#include <string>

struct A
{
    A(const std::string& pa) : a(pa) {printf("CTR: A address: %p\n", this) ;}
    std::string a;
};

struct B
{
    B(const std::string& pb) : b(pb) {printf("CTR: B address: %p\n", this) ;}
    std::string b;
};

// assumed data layout:
struct C {

    C() : a("astring"), b("bstring") {}
  // ...
  A a;
  // ...
  B b;
};

void approach1( A *pa, B *pb )
{

    printf("approach1: A address: %p B address: %p\n", pa, pb); 
    // compute offset:
    std::ptrdiff_t offset = static_cast<char*>((void*)pb) - static_cast<char*>((void*)pa);
    // then in some other function...
    // given offset and ptr to b, compute ptr to a:
    A *a = static_cast<A*>( (void*)(static_cast<char*>((void*)pb) + offset) );
    printf("approach1: a address: %p \n", a); 

    std::cout << "approach1: A->a=" << a->a << std::endl;
}


void approach2( A *pa, B *pb )
{
    printf("approach2: A address: %p B address: %p\n", pa, pb); 

    std::ptrdiff_t offset = reinterpret_cast<char*>(pb) - reinterpret_cast<char*>(pa);

    A *a = reinterpret_cast<A*>( reinterpret_cast<char*>(pb) + offset );
    printf("approach2: a address: %p \n", a); 
    std::cout << "approach2: A->a=" << a->a << std::endl;
}

main()
{
  C c;
  std::cout << c.a.a << std::endl;

  approach1(&c.a, &c.b);
  approach2(&c.a, &c.b);
}

我的计算机(uname -a Linux flood 3.13.0-33-generic #58-Ubuntu SMP Tue Jul 29 16:45:05 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux)与我的编译器(g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2)的输出是:

CTR: A address: 0x7fff249f0900
CTR: B address: 0x7fff249f0908
astring
approach1: A address: 0x7fff249f0900 B address: 0x7fff249f0908
approach1: a address: 0x7fff249f0910 
approach1: A->a=<GARBAGE>
approach2: a address: 0x7fff249f0910 

其中<GARBAGE>按预期包含...垃圾。

请参阅:http://ideone.com/U8ahAL