指针/整数算术(un)定义的行为

时间:2014-08-12 13:39:58

标签: c++ gcc clang undefined-behavior pointer-conversion

我有以下功能模板:

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的模糊或不可访问的基类,因此没有演员可以在这里提供帮助。

这是我的代码,但它可以用于我无法控制的类型(即我无法修改MostDerivedHeldAs)。该函数具有以下前提条件:

  • *original属于动态类型MostDerived
  • HeldAsMostDerivedMostDerived的直接或间接基类(忽略cv-qualifiation)
  • *held引用*original或其基类子对象之一。

让我们假设前提条件令人满意。 duplicate在这种情况下是否定义了行为?

C ++ 11 [expr.reinterpret.cast]说(大胆强调我的):

  

4指针可以显式转换为足以容纳它的任何整数类型。映射功能是   实现定义。 [注意:对于那些了解寻址结构的人来说,这并不奇怪   底层机器。 -end note ] ...

     

5可以将整数类型或枚举类型的值显式转换为指针。转换的指针   到一个足够大小的整数(如果在实现上存在任何这样的整数)并返回到相同的指针类型   将具有其原始价值; 指针和整数之间的映射是实现定义的。   [注意:除3.7.4.3中所述外,此类转换的结果不是安全派生的指针   值。 -end note ]

好吧,假设我的编译器是GCC(或Clang,因为它使用GCC的实现定义行为的定义)。引用GCC docs chapter 5关于C ++实现定义的行为:

  

... C语言的相应文档中记录了一些选项。见C Implementation。 ...

On chapter 4.7(C实现,数组和指针):

  

将指针转换为整数的结果,反之亦然(C90 6.3.4,C99和C11 6.3.2.3)。

     

如果指针表示大于整数类型,则从指针到整数的转换会丢弃最高有效位,如果指针表示小于整数类型,则签名扩展,否则位不变。

     

如果指针表示小于整数类型,则从整数到指针的强制转换丢弃最高有效位,如果指针表示大于整数类型,则根据整数类型的符号扩展,否则位不变

到目前为止,这么好。看来,因为我使用的std::uintptr_t保证对任何指针足够大,并且因为我处理相同的类型,copyHeld应该指向相同的HeldAs *copy的{​​{1}}子对象指向held内。

不幸的是,GCC文档中还有一个段落:

  

当从指针转换为整数并再次返回时,结果指针必须引用与原始指针相同的对象,否则行为未定义。也就是说,可能不会使用整数运算来避免指针运算的未定义行为,如C99和C11 6.5.6 / 8中所禁止的那样。

重击。所以现在似乎即使*original的值是根据前两段的规则计算的,第三个仍然会将其发送到Undefined-Behavior域。

我基本上有三个问题:

  1. 我的阅读是否正确且copyHeld的行为未定义?

  2. 这是哪种未定义的行为? “正式未定义,但无论如何都会做你想要的”,或“期望随机崩溃和/或自发的自焚”一个?

  3. 如果它确实是未定义的,有没有办法以明确定义的(可能依赖于编译器的)方式做这样的事情?

  4. 虽然就编译器而言,我的问题仅限于GCC(和Clang)行为,但我欢迎一个考虑所有类型硬件平台的答案,从普通桌面到异国情调。

1 个答案:

答案 0 :(得分:0)

通常的模式是在基类中放置clone() 然后每个派生类都可以实现自己的克隆版本。

class Base
{
     public:
        virtual Base*  clone() = 0;
};

class D: public Base
{
        virtual Base*  clone(){  return new D(*this);}
};