3

正如标题所说:

为什么在已删除指针上调用非虚拟成员函数是未定义的行为?

请注意,问题不会询问它是否是未定义的行为,而是询问为什么它是未定义的行为。


考虑以下程序

#include<iostream>
class Myclass
{
    //int i
    public:
      void doSomething()
      {
          std::cout<<"Inside doSomething";
          //i = 10;
      }
};

int main()
{
    Myclass *ptr = new Myclass;
    delete ptr;

    ptr->doSomething();

    return 0;
}

在上面的代码中,编译器this在调用成员函数时实际上并没有取消引用doSomething()。请注意,该函数不是虚函数,编译器通过将 this 作为第一个参数传递给函数将成员函数调用转换为通常的函数调用(据我所知,这是实现定义的)。他们可以这样做,因为编译器可以准确地确定在编译时调用哪个函数。所以实际上,通过删除指针调用成员函数不会取消引用this. 仅当this在函数体内访问任何成员时才会取消引用。(即:在上面访问的示例中取消注释代码i
如果在函数中未访问成员,则上述代码实际上不应调用未定义的行为。

那么为什么标准要求通过删除的指针调用非虚成员函数是一种未定义的行为,而事实上它可以可靠地说取消引用this应该是导致未定义行为的语句?仅仅是为了让语言的用户简单,标准只是对其进行概括,还是在这个任务中涉及一些更深层次的语义?

我的感觉是,也许因为它是实现定义了编译器如何调用成员函数,这可能是标准无法强制执行 UB 发生的实际点的原因。

有人可以确认吗?

4

4 回答 4

9

因为它可能可靠的情况很少,这样做仍然是一个难以言喻的愚蠢想法。定义行为没有任何好处。

于 2012-12-23T16:14:48.557 回答
6

那么为什么标准要求通过删除的指针调用非虚成员函数是一种未定义的行为,而实际上它可以可靠地说取消引用 this 应该是导致未定义行为的语句?

[expr.ref] 第 2 段说成员函数调用ptr->doSomething()等价于(*ptr).doSomething()调用非静态成员函数取消引用。如果指针无效,那是未定义的行为。

生成的代码是否真的需要针对特定​​情况取消引用指针并不相关,编译器建模的抽象机原则上会进行取消引用。

使语言复杂化以准确定义只要不访问任何成员就允许的情况几乎为零。在看不到函数定义的情况下,您不知道调用它是否安全,因为您无法知道该函数是否使用this

只是不要这样做,没有充分的理由,语言禁止这样做是一件好事。

于 2012-12-31T15:43:10.587 回答
5

在 C++ 语言中(根据 C++03),尝试使用无效指针的值已经导致未定义的行为。没有必要取消对 UB 的引用。只需读取指针值就足够了。当您仅尝试读取该值时导致 UB 的“无效值”概念实际上扩展到几乎所有标量类型,而不仅仅是指针。

delete该指针通常在该特定意义上无效之后,即读取据称指向刚刚被“删除”的东西的指针会导致未定义的行为。

int *p = new int();
delete p;
int *p1 = p; // <- undefined behavior

通过无效指针调用成员函数只是上述的一种特殊情况。指针用作隐式参数的参数this。传递指针是一个非引用参数是一种读取它的行为,这就是为什么在您的示例中行为未定义的原因。

所以,你的问题真的归结为为什么读取无效的指针值会导致未定义的行为。

好吧,这可能有许多特定于平台的原因。例如,在某些平台上,读取指针的行为可能会导致指针值被加载到某个专用的地址特定寄存器中。如果指针无效,硬件/操作系统可能会立即检测到它并触发程序故障。事实上,这就是我们流行的 x86 平台在段寄存器方面的工作方式。我们没有听到太多关于它的唯一原因是流行的操作系统坚持平面内存模型,它根本不主动使用段寄存器。


C++11 实际上声明取消引用无效指针值会导致未定义的行为,而无效指针值的所有其他用途会导致实现定义的行为。它还指出,在“复制无效指针”的情况下实现定义的行为可能会导致“系统生成的运行时错误”。因此,实际上有可能小心翼翼地穿过 C++11 规范的迷宫,并成功得出结论,即通过无效指针调用非虚拟方法应该会导致上述实现定义的行为。无论如何,“系统生成的运行时错误”的可能性总是存在的。

于 2012-12-23T16:44:38.750 回答
2

在这种情况下,取消引用this实际上是一个实现细节。我并不是说this指针不是由标准定义的,因为它是,但从语义抽象的角度来看,允许使用已被破坏的对象的目的是什么,只是因为存在一个极端情况练习它会“安全”吗?没有任何。所以不是。不存在对象,因此您不能在其上调用函数。

于 2013-01-06T14:57:12.037 回答