C++ 类成员函数
在进入正题之前,先贴一段代码。
#include <stdio>
class A{
private:
int a;
public:
void FuncA(){
printf("hello world!");
}
void FuncB(){
printf("hello world!,%d",a);
}
A(){
a = 0;
}
};
int main(){
A *pA = nullptr;
pA->FuncA();
pA->FuncB();
return 0;
}
然后单步调试运行结果是:pA->FuncA();
不会崩溃,但是pA->FuncB();
会崩溃。为啥。看上去两个几乎一样的函数调用,为啥一个可以运行一个不行。
C++中教导我们不可以去调用未初始化类的方法(其实这句话也不是全对)。这里明明没有被初始化pA的FuncA被调用没错。
- 解答
这里我们只需要比较一下两个函数的汇编,即能明显的得到这两个函数的区别。
首先看FuncA();我们可以看到将hello world!字符串首地址进行压栈操作之后立马调用了printf然后将字符串打印,然后校验rsp(一般debug环境下专有,在函数调用返回之前起平衡堆栈的作用)。没有涉及到动态内存的访问,FuncA()所在的代码段并没有引用任何的非法内存。
然后看FuncB(),这里的这三句话,将this指针转存到ecx,然后传递给printf,这里由于this指针为nullptr(因为pA为nullptr,所以取不到A非法内存访问崩溃)。
这里差别在汇编看来,除了最后的调用printf之外,只有FuncB进行了非法内存(a的指针)的访问,由于a的指针和pA的值一样,所以指向0地址的值,这样就造成了非法内存访问。
究其本质,其实C++在类成员函数调用时,差不多作可以这样理解:将一个void A::Func(….)转换为,void Func(….,void *pThis)形式,将自己this指针作为一个参数传递给自身的成员函数,然后在汇编中,通过ecx传递this指针的值,成员函数整个编译成二进制代码,成员函数通过基址+偏移的方式访问类的成员变量。
这样只要访问自己的成员变量的成员函数,在对象不存在,由于基址(就是this指针)非法,造成因为通过基址+偏移的方式访问的成员变量非法,这样就会造成非法内存访问。也并不是单纯的去调用未初始化类的方法一定会崩,关键看内存访问。
还有一条,就是类的代码段和数据段是独立的,即使类的数据域为null,他的类成员函数(包括各种构造析构函数,代码段)都是存在于编译之后的exe中的。类的数据为空,并不影响他代码段的使用。