(This is another question about undefined behaviour (UB). If this code 'works' on some compiler, then that means nothing in the land of UB. That is understood. But exactly at what line below do we cross into UB?)
(There are a number of very similar questions on SO already, e.g. (1) but I'm curious what can be safely done with the pointers before dereferencing them.)
Start off with a very simple Base class. No virtual
methods. No inheritance. (Maybe this can be extended to anything that's POD?)
struct Base {
int first;
double second;
};
And then a simple extension that adds (non-virtual
) methods and doesn't add any members. No virtual
inheritance.
struct Derived : public Base {
int foo() { return first; }
int bar() { return second; }
};
Then, consider the following lines. If there is some deviation from defined behaviour, I'd be curious to know which lines exactly. My guess is that we can safely perform much of the calculations on the pointers. Is it possible that some of these pointer calculations, if not fully defined, at least give us some sort of 'indeterminate/unspecified/implementation-defined' value that isn't entirely useless?
void foo () {
Base b;
void * vp = &b; // (1) Defined behaviour?
cout << vp << endl; // (2) I hope this isn't a 'trap value'
cout << &b << endl; // (3a) Prints the same as the last line?
// (3b) It has the 'same value' in some sense?
Derived *dp = (Derived*)(vp);
// (4) Maybe this is an 'indeterminate value',
// but not fully UB?
cout << dp << endl; // (5) Defined behaviour also? Should print the same value as &b
Edit: If the program ended here, would it be UB? Note that, at this stage, I have not attempted to do anything with dp
, other than print the pointer itself to the output. If simply casting is UB, then I guess the question ends here.
// I hope the dp pointer still has a value,
// even if we can't dereference it
if(dp == &b) { // (6) True?
cout << "They have the same value. (Whatever that means!)" << endl;
}
cout << &(b.second) << endl; (7) this is definitely OK
cout << &(dp->second) << endl; // (8) Just taking the address. Is this OK?
if( &(dp->second) == &(b.second) ) { // (9) True?
cout << "The members are stored in the same place?" << endl;
}
}
I'm slightly nervous about (4) above. But I assume that it's always safe to cast to and from void pointers. Maybe the value of such a pointer can be discussed. But, is it defined to do the cast, and to print the pointer to cout
?
(6) is important also. Will this evaluate to true?
In (8), we have the first time this pointer is being dereferenced (correct term?). But note that this line doesn't read from dp->second
. It's still just an lvalue and we take its address. This calculation of the address is, I assume, defined by simple pointer arithmetic rules that we have from the C language?
If all of the above is OK, maybe we can prove that static_cast<Derived&>(b)
is OK, and will lead to a perfectly usable object.