为什么可以通过野指针调用成员函数
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 机器上运行这段代码并且输出完全相同,这意味着我总能得到 start
和 Segmentation 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 的指针,这会导致分段错误。
#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 机器上运行这段代码并且输出完全相同,这意味着我总能得到 start
和 Segmentation 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 的指针,这会导致分段错误。