Aar*_*aid 6 c++ undefined-behavior language-lawyer
(这是关于未定义行为(UB)的另一个问题.如果这个代码在某些编译器上"起作用",那么在UB的土地上就没有任何意义.这是可以理解的.但正好在下面的哪一行我们会进入UB?)
(关于SO已经有很多非常相似的问题了,例如(1) 但是我很好奇在取消引用它们之前可以用指针安全地完成什么.)
从一个非常简单的Base类开始.没virtual办法.没有继承.(也许这可以扩展到任何POD?)
struct Base {
int first;
double second;
};
Run Code Online (Sandbox Code Playgroud)
然后是一个简单的扩展,添加(非virtual)方法,不添加任何成员.没有virtual继承.
struct Derived : public Base {
int foo() { return first; }
int bar() { return second; }
};
Run Code Online (Sandbox Code Playgroud)
然后,考虑以下几行.如果与定义的行为有一些偏差,我很想知道确切地知道哪些行.我的猜测是我们可以安全地对指针执行大部分计算.是否有可能这些指针计算中的一些,如果没有完全定义,至少会给我们某种"不确定/未指定/实现定义"的值,这些值并非完全没用?
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
Run Code Online (Sandbox Code Playgroud)
编辑:如果程序在这里结束,它会是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;
}
}
Run Code Online (Sandbox Code Playgroud)
我对上面的(4)有点紧张.但我认为对于void指针进行转换总是安全的.也许可以讨论这种指针的价值.但是,是否定义了执行转换,并将指针打印到cout?
(6)也很重要.这会评估为真吗?
在(8)中,我们第一次对该指针进行解引用(正确的术语?).但请注意,此行不读取dp->second.它仍然只是一个左值,我们采取其地址.我假设这个地址的计算是由我们从C语言得到的简单指针算术规则定义的?
如果以上所有都可以,也许我们可以证明这static_cast<Derived&>(b)是可以的,并且将导致一个完全可用的对象.
void *始终保证有效,并且保证指针在往返Base *-> void *-> Base *(C++11 \xc2\xa75.2.9 \xc2\xb613) 中存活下来;vp是一个有效的指针,所以应该没有任何问题。A。尽管打印指针是实现定义的1,但打印的值应该相同:事实上,operator<<默认情况下仅重载于const void *,因此当您编写时cout<<&b,您将转换为const void *无论如何operator<<,即在两种情况下看到的都会&b转换为const void *。
b. 是的,如果我们采用“具有相同值”的唯一合理定义 - 即它与运算符进行比较==;事实上,如果您比较vp和&b,==结果是true,如果您转换vp为Base *(由于我们在 1 中所说的),还是转换&b为void *。
这两个结论都来自 \xc2\xa74.10 \xc2\xb62,其中指定任何指针都可以转换为void *(对通常的cv限定的东西取模),并且结果 \xc2\xab 指向对象[...]所在的存储位置\xc2\xbb 1
这很棘手;C 风格的强制转换相当于 a static_cast,这将很高兴地允许将 \xc2\xab"指向cv1 B [...] 的指针强制转换为 [...] "指向 *cv2 的指针D",其中是从\xc2D派生的类B\xbb (\xc2\xa75.2.9, \xc2\xb611;还有一些额外的约束,但这里满足了);但是:
\n\n\n如果指向cv1
\nB\xe2\x80\x9d 的\xe2\x80\x9c 类型指针的纯右值指向的 aB实际上是类型 的对象的子对象D,则生成的指针指向类型 的封闭对象D。否则,转换的结果是未定义的。
(强调已添加)
\n\n所以,在这里你的演员是允许的,但结果是未定义的......
...这导致我们打印它的值;由于转换的结果未定义,因此您可能会得到任何结果。由于指针可能允许具有陷阱表示(至少在 C99 中,我只能在 C++11 标准中找到对“陷阱”的稀疏引用,但我认为这种行为可能应该已经从 C89 继承),您甚至可以只需读取此指针并通过 打印它就会发生崩溃operator<<。
如果遵循,则 6、8 和 9 也没有意义,因为您使用的是未定义的结果。
\n\n此外,即使强制转换有效,严格别名(\xc2\xa73.10、\xc2\xb610)也会阻止您对指向的对象执行任何有意义的操作,因为仅当动态类型时才允许Base通过指针对对象进行别名对象Derived的Base实际上是Derived; 任何偏离 \xc2\xa73.10 \xc2\xb610 中指定的异常的情况都会导致未定义的行为。
笔记:
\n\noperator>>委托到num_put概念上委托给printfwith %p,其描述归结为“实现定义”。
这消除了我的担心,即理论上,当转换为 时,邪恶的实现可能会返回不同但相同的值void *。
| 归档时间: |
|
| 查看次数: |
188 次 |
| 最近记录: |