为什么可以通过野指针调用成员函数

Why can I call a member function through a wild pointer

#include <iostream>
using namespace std;

class Car{
public:
    void start() {
        std::cout << "start" << std::endl;
    };
    virtual void stop() {
        std::cout << "stop" << std::endl;
    };
};

int main() {
    Car* p;// = new Car;
    p->start();
    p->stop();
    return 0;
}

这段代码给我的输出如下:

me@Ubuntu:~/tmp$ ./a.out
start
Segmentation fault (core dumped)

OK,我理解p是一个野指针,所以这应该是一个未定义的行为。但是我已经尝试 运行 在其他一些 X86_64 机器上运行这段代码并且输出完全相同,这意味着我总能得到 startSegmentation fault

我想知道为什么我总能得到那个start

我用 gdb 调试了这段代码:

(gdb) b main
Breakpoint 1 at 0x40084e: file main.cpp, line 16.
(gdb) r
Starting program: /home/zyh/tmp/a.out
Breakpoint 1, main () at main.cpp:16
16          p->start();
(gdb) display /5i $pc
1: x/5i $pc
=> 0x40084e <main()+8>: mov    -0x8(%rbp),%rax
   0x400852 <main()+12>:        mov    %rax,%rdi
   0x400855 <main()+15>:        callq  0x4008c8 <Car::start()>
   0x40085a <main()+20>:        mov    -0x8(%rbp),%rax
   0x40085e <main()+24>:        mov    (%rax),%rax

大家可以看到,我们发现有这么一行:callq 0x4008c8 <Car::start()>.

所以,我知道这应该是一个未定义的行为,但我在想是否有某种原因可以解释为什么成员函数 start 可以被野指针调用。

OK, I understand that p is a wild pointer

这是不寻常的术语。未初始化的值被认为是不确定的。

so this should be an undefined behavior

这确实是UB。读取不确定值的行为是未定义的。

I'm wondering why I can always get that start.

因为程序的行为未定义。

请注意,到目前为止,您在实验中获得 "start" 并不意味着您将永远获得它。不能根据未定义的行为做出假设。

简答: "start()" 是非虚函数。

说明: 对于非虚函数,C++根据指针决定调用什么函数 type - not 实际引用对象的类型。

所以它通过指针类型找到函数,然后,由于您不使用 "start()" 中的引用对象(您不访问任何数据成员),您的程序不会产生分段错误。

但是,如果您调用虚成员函数,例如"stop()",C++ 会使用dynamic dispatch 来确定要使用的函数。 因此它试图从对象中获取指向 virtual method table 的指针,这会导致分段错误。