(这是有关未定义行为(UB)的另一个问题。如果此代码在某些编译器上“起作用”,则在UB领域中没有任何意义。这是可以理解的。但是,我们到底在哪一行进入UB?)
(SO上已经有很多非常相似的问题,例如(1),但我很好奇在取消引用指针之前可以安全地完成哪些操作。)
从一个非常简单的Base类开始。没有virtual
方法。没有继承。 (也许可以将其扩展到任何POD吗?)
struct Base {
int first;
double second;
};
然后是一个简单的扩展,它添加了(non-
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)感到有些紧张。但是我认为在空指针之间进行强制转换始终是安全的。也许可以讨论这样一个指针的值。但是,是否定义了进行强制类型转换以及将指针打印到
cout
的定义?(6)也很重要。这会评估为真吗?
在(8)中,我们是第一次取消对该指针的引用(正确的术语?)。但是请注意,此行不会从
dp->second
中读取。它仍然只是左值,我们取其地址。我认为,这种地址计算是由我们从C语言获得的简单指针算术规则定义的?如果以上所有操作都可以,那么我们也许可以证明
static_cast<Derived&>(b)
是可以的,并且会导致一个完美可用的对象。 最佳答案
void *
的转换都可以正常工作,并且保证指针在往返Base *
-> void *
-> Base *
的过程中仍然有效(C++ 11§5.2.9¶13); vp
是有效的指针,因此应该没有任何问题。 operator<<
仅对const void *
重载,因此,当您编写cout<<&b
时,无论如何都将转换为const void *
,即,两种情况下operator<<
看到的都是&b
转换为const void *
。b。是的,如果我们采用“具有相同值”的唯一明智的定义-即它与
==
运算符进行比较;实际上,如果将vp
和&b
与==
进行比较,则结果是true
,如果您将vp
转换为Base *
(由于我们在1中所说),又将&b
转换为void *
。这两个结论均来自§4.10¶2,其中指定了任何指针都可以转换为
void *
(对通常的cv限定的东西进行模运算),结果«指向对象[...]的存储位置的开始。 ]居住»1 static_cast
,这将很乐意允许将«“指向cv1 B
的指针[...]转换为” * cv2 D
的指针“,其中D
是派生自B
的类»(第5.2.9节,¶11;还有一些其他约束,但是在这里可以满足);但是:(强调)
因此,这里允许您进行强制类型转换,但是结果是不确定的...
operator<<
进行打印即可导致崩溃。 如果遵循,则6、8和9也没有意义,因为您使用的是不确定的结果。
另外,即使强制转换有效,严格的别名(第3.10节,第10节)也会阻止您对指向的对象执行任何有意义的操作,因为仅当
Base
对象的动态类型时才允许通过Derived
指针对Base
对象进行别名实际上是Derived
;偏离§3.10¶10中指定的异常的任何结果都将导致未定义的行为。笔记:
operator>>
委托(delegate)给num_put
,后者从概念上委托(delegate)给printf
和%p
,其描述可以归结为“实现定义”。 void *
时,邪恶的实现理论上可能返回不同但等效的值。 关于c++ - 如果“派生”没有向Base添加新成员(并且是POD),那么可以安全地进行哪种类型的指针强制转换和取消引用?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19741843/