Reputation: 4173
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.)
static_cast<char*>((void*)ptr))
reinterpret_cast<char*>(ptr)
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_cast
ing a pointer to and from void*
preserves the address," whereas reinterpret_cast<>
guarantees less (see accepted answer at 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).
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.
Upvotes: 6
Views: 2891
Reputation: 13288
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).
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)).
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:
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
Upvotes: 6
Reputation: 35408
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:
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
Upvotes: 0