在 NULL 指针上访问类成员

新手上路,请多包涵

我正在试验 C++,发现下面的代码很奇怪。

 class Foo{
public:
    virtual void say_virtual_hi(){
        std::cout << "Virtual Hi";
    }

    void say_hi()
    {
        std::cout << "Hi";
    }
};

int main(int argc, char** argv)
{
    Foo* foo = 0;
    foo->say_hi(); // works well
    foo->say_virtual_hi(); // will crash the app
    return 0;
}

我知道虚拟方法调用会崩溃,因为它需要 vtable 查找并且只能使用有效对象。

我有以下问题

  1. 非虚方法 say_hi 如何在 NULL 指针上工作?
  2. 对象 foo 在哪里分配?

有什么想法吗?

原文由 Navaneeth K N 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 421
2 个回答

对象 foo 是类型为 Foo* 的局部变量。就像任何其他局部变量一样,该变量可能会在 main 函数的堆栈上分配。但是存储在 foo 中的 是一个空指针。它没有指向任何地方。在任何地方都没有 Foo 类型的实例。

要调用虚函数,调用者需要知道该函数是在哪个对象上被调用的。那是因为对象本身告诉我们应该真正调用哪个函数。 (这通常通过给对象一个指向 vtable 的指针、一个函数指针列表来实现,而调用者只知道它应该调用列表中的第一个函数,而不事先知道该指针指向的位置。)

但是要调用一个非虚拟函数,调用者不需要知道所有这些。编译器确切地知道哪个函数将被调用,因此它可以生成一个 CALL 机器代码指令直接转到所需的函数。它只是将一个指向函数调用对象的指针作为隐藏参数传递给函数。换句话说,编译器将您的函数调用转换为:

 void Foo_say_hi(Foo* this);

Foo_say_hi(foo);

现在,由于该函数的实现从不引用其 this 参数所指向的对象的任何成员,因此您有效地避开了取消引用空指针的子弹,因为您从不取消引用空指针。

正式地,在空指针上调用 任何 函数——甚至是非虚拟函数——都是未定义的行为。未定义行为的允许结果之一是您的代码似乎完全按照您的预期运行。 应该依赖它,尽管有时您会从编译器供应商那里找到依赖它的库。但是编译器供应商的优势在于能够为原本未定义的行为添加进一步的定义。不要自己做。

原文由 Rob Kennedy 发布,翻译遵循 CC BY-SA 2.5 许可协议

say_hi() 成员函数通常由编译器实现为

void say_hi(Foo *this);

由于您不访问任何成员,因此您的调用成功(即使您根据标准输入未定义的行为)。

Foo 根本没有被分配。

原文由 Pontus Gagge 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题