以下三种强制转换之间提取用于指针算术的原始字节指针之间有什么区别吗?(假设一个 char 为 1 字节的平台。
-
static_cast<char*>((void*)ptr))
-
reinterpret_cast<char*>(ptr)
- (更新(或:
static_cast<char*>(static_cast<void*>(ptr))
我应该更喜欢哪个?
更详细地说...
给定指向类中两个成员对象的指针,我想计算从一个到另一个成员对象的偏移量,以便我可以在给定偏移量的情况下重建一个成员的地址和另一个成员的地址。
// 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_cast
指向和传出void*
的指针会保留地址",而reinterpret_cast<>
保证更少(请参阅链接中的已接受答案(。
我想知道最干净的方法是什么。
更新:目的说明
许多人问计算这些偏移量的目的是什么。目的是构造实例偏移量的元类表。运行时反射机制使用它来自动构建 GUI 和持久性(偏移量不序列化,仅用于遍历结构(。该代码已经生产了 15 年以上。出于这个问题的目的,我只想知道计算指针偏移量的最便携方法。我无意对元类系统的工作方式进行重大更改。此外,我通常也对执行此操作的最佳方法感兴趣,因为我还有其他用途(例如共享内存代码的差异指针(。
注意:我不能使用offsetof()
,因为在我的实际代码中,我只有指向实例a
和b
的指针,我不一定有包含对象的类型c
或其他静态信息来使用offsetof()
。我只能假设a
和b
是同一对象的成员。
这两个将导致相同的结果,因此差异主要是语义上的,并且reinterpret_cast
具有您想要的操作的含义,再加上只需要一个强制转换而不是两个(并且代码中的强制转换越少越好(。
reinterpret_cast
5.2.10/7: 对象指针可以显式转换为不同类型的对象指针。当 prvalue v of 对象指针类型转换为对象指针类型"指向 cv T",结果为 static_cast
(static_cast (v((。
因此,除非中年平台上出现异国情调的随机低级不同行为,否则您绝对应该选择:
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
我不建议计算类成员地址之间的偏移距离。编译器可能会注入填充数据,或者即使它正在工作,它也只会以相同的方式工作,仅适用于在该特定主机上运行的特定编译器。应用这种做法时,有许多错误来源。例如,如果您必须在多个虚拟继承中处理著名的虚拟表和内存布局怎么办?这将完全使您的解决方案无法使用。
所以回到根源:你为什么要这样做?也许有更好的解决方案。
编辑/更新
感谢您向我们解释原因。这是一种非常有趣的方法,我直到现在都没有看到。我今天学到了一些东西。
但是,我仍然坚持我的观点,即应该有一种更简单的方法来解决这个问题。作为证明的概念,我写了一个小应用程序,只是为了看看你的哪些方法有效。对我来说,它们都不起作用。
该应用程序是您的方法之一,这里是:
#include <iostream>
#include <stdio.h>
#include <string>
struct A
{
A(const std::string& pa) : a(pa) {printf("CTR: A address: %pn", this) ;}
std::string a;
};
struct B
{
B(const std::string& pb) : b(pb) {printf("CTR: B address: %pn", 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: %pn", 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: %pn", 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