为字节指针差异重新解释t_cast(p)或static_cast(void*)p),哪个更好?

时间:2022-12-29 02:57:47

Is there any difference between the following three casts for extracting raw byte pointers for use in pointer arithmetic? (assume a platform where char is 1 byte.)

下面三种类型的转换是否有区别,用于提取用于指针算法的原始字节指针?(假设平台的char是1字节)

  1. static_cast<char*>((void*)ptr))
  2. static_cast < char * >(void * ptr))
  3. reinterpret_cast<char*>(ptr)
  4. reinterpret_cast < char * >(ptr)
  5. (updated) or: static_cast<char*>(static_cast<void*>(ptr))
  6. (更新)或:static_cast < char * >(static_cast < void * >(ptr))

Which should I prefer?

我应该选择哪个?

In more detail...

在更多的细节……

Given pointers to two member objects in a class, I would like to compute an offset from one to the other, so that I can reconstruct the address of one member given an offset and the address of the other member.

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

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

The code that I use at the moment is along the lines of:

我现在使用的代码是:

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);
}

I would like to know whether the following is better (or worse):

我想知道以下是好还是坏:

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 );
}

Are the two methods entirely equivalent? Are they equally portable?

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

My impression is that approach1() is more portable, because "static_casting a pointer to and from void* preserves the address," whereas reinterpret_cast<> guarantees less (see accepted answer at link).

我的印象是approach1()更具有可移植性,因为“static_cast一个指向和来自void*的指针保持了地址”,而reinterpretation t_cast<>保证的更少(请参阅link上接受的答案)。

I would like to know what the cleanest way to do this is.

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

Update: Explanation of Purpose

更新:解释的目的

A number of people have asked what is the purpose of computing these offsets. The purpose is to construct a meta-class table of instance offsets. This is used by a runtime reflection mechanism for automatic GUI building and persistance (the offsets are not serialized, just used to traverse the structure). The code has been in production for over 15 years. For the purposes of this question I just want to know the most portable way of computing the pointer offsets. I have no intention of making large changes to the way the metaclass system works. In addition, I'm also generally interested in the best way to do this, as I have other uses in mind (e.g. difference pointers for shared memory code).

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

NOTE: I can not use offsetof() because in my actual code I only have the pointers to instances a and b, I don't necessarily have the type of the containing object c or other static info to use offsetof(). All I can assume is that a and b are members of the same object.

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

2 个解决方案

#1


6  

These two will lead to the same result so the difference is mostly semantical, and reinterpret_cast has exactly the meaning of the operation you want, plus the fact that only one cast is required instead of two (and the less cast you have in your code the better).

这两个将导致相同的结果,因此差异主要是语义上的,reinterpretation t_cast具有您想要的操作的意义,另外,只需要一个cast而不是两个cast(您的代码中的cast越少越好)。

reinterpret_cast

reinterpret_cast

5.2.10/7: An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast< cv T* >(static_cast< cv void* >(v)).

5.2.10/7:可以显式地将对象指针转换为不同类型的对象指针。当对象指针类型的prvalue v转换为对象指针类型“指针指向cv T”时,结果是static_cast< cv T* >(static_cast< cv void* >(v)))。

So except if an exotique random low level different behaviour appears on a middle-age platform, you should definitely go with:

所以,除非在一个中年的平台上出现了一种奇特的、随机的、低层次的行为,你一定要去:

reinterpret_cast<char*>(ptr);

In general.

一般来说。

That said, why don't you use uintptr_t in your case ? it's even more apropriate, you need no pointer:

也就是说,为什么不使用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 );
}

For additional information see:

更多信息请见:

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

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

#2


0  

I do not recommend calculating offset distances between class members' addresses. Either the compiler might inject padding data, or even if it is working it will work the same way only for that specific compiler running on that specific host. There are a multitude sources of error when applying this practice. For example what if you have to deal with the famous Virtual tables and memory layout in multiple virtual inheritance ? This will totally render your solution unusable.

我不建议计算类成员地址之间的偏移距离。编译器可以注入填充数据,或者即使它在工作,它也只对运行在特定主机上的特定编译器起同样的作用。在应用这种实践时,有很多错误的来源。例如,如果您必须处理多个虚拟继承中的著名的虚拟表和内存布局,该怎么办?这将使您的解决方案完全无法使用。

So back to the roots: Why are you trying to do this? Maybe there is a better solution.

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

EDIT/Update

编辑/更新

Thanks for explaining us the reason. It is a very interesting approach I did not see till now. I have learned something today.

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

However, I still stick to my point that there should be a much more easier way of handling this. And just as a concept of proof, I wrote a small application just to see which of your methods is working. For me neither of them work.

然而,我仍然坚持我的观点,应该有一种更简单的方法来处理这个问题。作为一个证明的概念,我写了一个小的应用程序,看看你的方法中哪个是有效的。对我来说,他们都不工作。

The application is a slightly expanded one of your methods, here it is:

应用程序稍微扩展了您的方法,这里是:

#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);
}

The output of it on my computer (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) with my compiler (g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2) is:

它在我的计算机上的输出(uname -a Linux flood 3.13.0-33-generic #58-Ubuntu SMP tul 29 16:45:05 UTC 2014 x86_64 x86_64 x86_64 x86_64 x86_64 GNU/Linux)和我的编译器(Ubuntu 4.8.2-19ubuntu1)是:

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 

where <GARBAGE> as expected contains ... garbage.

其中 如预期所示包含…垃圾。

Please see at: http://ideone.com/U8ahAL

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

#1


6  

These two will lead to the same result so the difference is mostly semantical, and reinterpret_cast has exactly the meaning of the operation you want, plus the fact that only one cast is required instead of two (and the less cast you have in your code the better).

这两个将导致相同的结果,因此差异主要是语义上的,reinterpretation t_cast具有您想要的操作的意义,另外,只需要一个cast而不是两个cast(您的代码中的cast越少越好)。

reinterpret_cast

reinterpret_cast

5.2.10/7: An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of object pointer type is converted to the object pointer type “pointer to cv T”, the result is static_cast< cv T* >(static_cast< cv void* >(v)).

5.2.10/7:可以显式地将对象指针转换为不同类型的对象指针。当对象指针类型的prvalue v转换为对象指针类型“指针指向cv T”时,结果是static_cast< cv T* >(static_cast< cv void* >(v)))。

So except if an exotique random low level different behaviour appears on a middle-age platform, you should definitely go with:

所以,除非在一个中年的平台上出现了一种奇特的、随机的、低层次的行为,你一定要去:

reinterpret_cast<char*>(ptr);

In general.

一般来说。

That said, why don't you use uintptr_t in your case ? it's even more apropriate, you need no pointer:

也就是说,为什么不使用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 );
}

For additional information see:

更多信息请见:

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

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

#2


0  

I do not recommend calculating offset distances between class members' addresses. Either the compiler might inject padding data, or even if it is working it will work the same way only for that specific compiler running on that specific host. There are a multitude sources of error when applying this practice. For example what if you have to deal with the famous Virtual tables and memory layout in multiple virtual inheritance ? This will totally render your solution unusable.

我不建议计算类成员地址之间的偏移距离。编译器可以注入填充数据,或者即使它在工作,它也只对运行在特定主机上的特定编译器起同样的作用。在应用这种实践时,有很多错误的来源。例如,如果您必须处理多个虚拟继承中的著名的虚拟表和内存布局,该怎么办?这将使您的解决方案完全无法使用。

So back to the roots: Why are you trying to do this? Maybe there is a better solution.

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

EDIT/Update

编辑/更新

Thanks for explaining us the reason. It is a very interesting approach I did not see till now. I have learned something today.

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

However, I still stick to my point that there should be a much more easier way of handling this. And just as a concept of proof, I wrote a small application just to see which of your methods is working. For me neither of them work.

然而,我仍然坚持我的观点,应该有一种更简单的方法来处理这个问题。作为一个证明的概念,我写了一个小的应用程序,看看你的方法中哪个是有效的。对我来说,他们都不工作。

The application is a slightly expanded one of your methods, here it is:

应用程序稍微扩展了您的方法,这里是:

#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);
}

The output of it on my computer (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) with my compiler (g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2) is:

它在我的计算机上的输出(uname -a Linux flood 3.13.0-33-generic #58-Ubuntu SMP tul 29 16:45:05 UTC 2014 x86_64 x86_64 x86_64 x86_64 x86_64 GNU/Linux)和我的编译器(Ubuntu 4.8.2-19ubuntu1)是:

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 

where <GARBAGE> as expected contains ... garbage.

其中 如预期所示包含…垃圾。

Please see at: http://ideone.com/U8ahAL

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