(这是关于未定义行为 (UB) 的另一个问题。如果此代码在某些编译器上“工作”,那么这在 UB 领域毫无意义。这是可以理解的。但是我们到底在下面哪一行进入 UB?)
(关于 SO 已经有许多非常相似的问题,例如(1) ,但我很好奇在取消引用它们之前可以安全地使用指针做什么。)
从一个非常简单的 Base 类开始。没有virtual
方法。没有继承。(也许这可以扩展到 POD 的任何东西?)
struct Base {
int first;
double second;
};
然后是一个简单的扩展,它添加(非virtual
)方法并且不添加任何成员。没有virtual
继承。
struct Derived : public Base {
int foo() { return first; }
int bar() { return second; }
};
然后,考虑以下几行。如果与定义的行为有一些偏差,我很想知道究竟是哪些行。我的猜测是我们可以安全地对指针执行大部分计算。是否有可能这些指针计算中的一些,如果没有完全定义,至少给我们一些不是完全无用的“不确定/未指定/实现定义”值?
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
编辑:如果程序在这里结束,会是 UB 吗?请注意,在这个阶段,除了将指针本身打印到输出之外,我没有尝试对 做任何事情。dp
如果只是铸造是UB,那么我想问题到此结束。
// 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;
}
}
我对上面的(4)有点紧张。但我认为在 void 指针之间进行转换总是安全的。也许可以讨论这样一个指针的值。但是,是否定义为进行强制转换并将指针打印到cout
?
(6)也很重要。这会评估为真吗?
在(8)中,我们第一次取消引用了这个指针(正确的术语?)。但请注意,此行不是从dp->second
. 它仍然只是一个左值,我们获取它的地址。我假设这种地址计算是由我们从 C 语言中获得的简单指针算术规则定义的?
如果以上都OK,也许我们可以证明它static_cast<Derived&>(b)
是OK的,并且会导致一个完全可用的对象。