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

Alo*_*ave 3 c++ undefined-behavior dereference language-lawyer

正如标题所说:

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

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


考虑以下程序:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

那么为什么通过删除指针调用非虚拟成员函数的标准命令是未定义的行为,实际上它可以可靠地说解除引用this应该是应该导致未定义行为的语句?仅仅为了语言用户的简单起见,标准只是概括它还是在这个任务中涉及更深层次的语义?

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

有人可以证实吗?

Pup*_*ppy 9

因为它可能是可靠的案件数量如此之少,并且这样做仍然是一个不可思议的愚蠢想法.定义行为没有任何好处.

  • +1.有充分的理由支持*静态*类成员(最后我检查它仍然是)没有'这个'.它在过去为我节省了大量的打字.但对于普通会员(虚拟或非虚拟),我认为没有任何好处. (3认同)

Jon*_*ely 6

那么为什么通过删除指针调用非虚拟成员函数的标准命令是一个未定义的行为,实际上它可以可靠地说解除引用它应该是应该导致未定义行为的语句?

[expr.ref]第2段说成员函数调用ptr->doSomething()等同于(*ptr).doSomething()调用非静态成员函数一个解除引用.如果指针无效则表示未定义的行为.

生成的代码是否实际需要取消引用特定情况的指针是不相关的,编译器模型的抽象机器原则会进行解除引用.

使语言复杂化以准确定义哪些情况将被允许,只要它们不访问任何成员就几乎没有任何好处.在您无法看到函数定义的情况下,您不知道调用它是否安全,因为您无法知道函数是否使用this.

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


AnT*_*AnT 5

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

delete指针通常在该特定意义上无效之后,即,读取指向已被"删除"的东西的指针导致未定义的行为.

int *p = new int();
delete p;
int *p1 = p; // <- undefined behavior
Run Code Online (Sandbox Code Playgroud)

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

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

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


C++ 11实际上声明解除引用无效指针值会导致未定义的行为,而无效指针值的所有其他使用都会导致实现定义的行为.它还指出,在"复制无效指针"的情况下,实现定义的行为可能导致"系统生成的运行时故障".因此,实际上可以通过C++ 11规范的迷宫仔细操纵一个方法,并成功地得出结论,通过无效指针调用非虚方法应该导致上面提到的实现定义的行为.无论如何,"系统生成的运行时故障"的可能性总是存在.