我有以下功能模板:
template <class MostDerived, class HeldAs>
HeldAs* duplicate(MostDerived *original, HeldAs *held)
{
// error checking omitted for brevity
MostDerived *copy = new MostDerived(*original);
std::uintptr_t distance = reinterpret_cast<std::uintptr_t>(held) - reinterpret_cast<std::uintptr_t>(original);
HeldAs *copyHeld = reinterpret_cast<HeldAs*>(reinterpret_cast<std::uintptr_t>(copy) + distance);
return copyHeld;
}
目的是复制特定类型的对象并将其返回为与输入相同的子对象“持有”。请注意,原则上,HeldAs
可以是 的模棱两可或不可访问的基类MostDerived
,因此在这里没有强制转换可以提供帮助。
这是我的代码,但它可以用于我无法控制的类型(即我不能修改MostDerived
or HeldAs
)。该函数具有以下前提条件:
*original
是动态类型的MostDerived
HeldAs
是MostDerived
或直接或间接的基类MostDerived
(忽略 cv 限定)*held
指*original
或其基类子对象之一。
让我们假设先决条件得到满足。在这种情况下是否duplicate
有定义的行为?
C++11 [expr.reinterpret.cast] 说(我的粗体强调):
4 指针可以显式转换为任何大到足以容纳它的整数类型。映射函数是实现定义的。[注意:对于那些知道底层机器的寻址结构的人来说,这并不奇怪。——尾注] ...
5 整数类型或枚举类型的值可以显式转换为指针。转换为足够大小的整数(如果实现中存在这样的整数)并返回相同指针类型的指针将具有其原始值;指针和整数之间的映射是由实现定义的。[注意:除 3.7.4.3 中描述的情况外,这种转换的结果不会是安全派生的指针值。——尾注]
好的,假设我的编译器是 GCC(或 Clang,因为它使用 GCC 对实现定义的行为的定义)。引用GCC 文档第 5 章关于 C++ 实现定义的行为:
... 一些选择记录在 C 语言的相应文档中。请参阅C 实现。...
到第 4.7 章(C 实现、数组和指针):
将指针转换为整数或反之亦然的结果(C90 6.3.4、C99 和 C11 6.3.2.3)。
如果指针表示大于整数类型,则从指针到整数的强制转换丢弃最高有效位,如果指针表示小于整数类型,则符号扩展,否则位不变。
如果指针表示小于整数类型,则从整数到指针的强制转换丢弃最高有效位,如果指针表示大于整数类型,则根据整数类型的符号进行扩展,否则位不变。
到目前为止,一切都很好。似乎因为我使用std::uintptr_t
which 保证对于任何指针都足够大,并且因为我正在处理相同的类型,所以copyHeld
应该指向as的相同HeldAs
子对象。*copy
held
*original
不幸的是,GCC 文档中还有一段:
当从指针转换为整数并再次转换时,结果指针必须引用与原始指针相同的对象,否则行为未定义。也就是说,不能使用整数算术来避免指针算术的未定义行为,如 C99 和 C11 6.5.6/8 中所禁止的。
威猛。所以现在看来,即使copyHeld
是按照前两段的规则计算的值,第三段仍然将其发送到 Undefined-Behaviour 领域。
我基本上有三个问题:
我的阅读是否正确以及
duplicate
未定义的行为?这是哪种未定义行为?“正式未定义,但无论如何都会做你想做的事”的那种,还是“预期随机崩溃和/或自发自焚”的那种?
如果它真的是未定义的,有没有办法以明确定义(可能依赖于编译器)的方式来做这样的事情?
虽然就编译器而言,我的问题仅限于 GCC(和 Clang)行为,但我欢迎一个考虑各种硬件平台的答案,从常见的桌面到异国情调的桌面。