探索C++类的内存结构、访问机制。并且探索编译在32位与64位下的不同。
在x64的环境下,开始编译调试。首先可以看到程序输出为: 然后用vs可以查看p1的内存地址为0x00000297f45f95e0,找到这块地址。 可以看到其内存结构与结构体一样,一是小端的存储方式,二是同样需要内存对齐 其中
内存地址变量E0-E3c加上补全E4-E7publicNum1E8-EFvec.zpF0-F7vec.len 加上补全F8-FFpriNum1加上补全同理我们可以分析在x86环境下的内存结构 在知道内存存储方式后,我们可以利用这一点,在设计类的时候可以节省空间。
我们可以看到内存里成员变量都有,但是没有成员函数指针,我们通过反汇编可以看到 p1与p2的成员函数是跳转到同一个地址
所以同样的类,不同的实例,调用的函数体是相同的,只不过传入的参数不同,其中默认的一个传入参数就是当前这个实例的的指针。
举一个不正确的例子:
class classA { public: int a=1; static int sa = 3; char c = 'a'; static int fun(int input) { return a + input; }; };这里会报两个错误: 一个是静态变量sa不能在内部被初始化,要不就定义为常量。 二个是静态成员函数中无法调用成员变量a。 带着疑问我们开始分析内存构造与访问机制。
首先写个正确的例子
class classA { public: int a=1; static int sa ; int c = 2; static int fun(int input) { return input; }; }; //初始化静态变量,static 成员变量必须在类声明的外部初始化 int classA::sa = 8; int main() { classA *p = new classA; cout << "size:" << sizeof(*p) << endl; int b = p->fun(23); while (1); }我们得到的size为8 我们查看p指向的内容:
地址内容0x000002705197FF4001 00 00 00 02 00 00 00我们好像只看到了变量a和c,没有看到sa。然而我们可以在全局/静态存储区见到他。所以这个变量的属性应该算是全局变量了。无论实例化了多少个对象,他们sa变量地址都是这一个。 然后我们分析成员函数static int fun
我们可以看到他的反汇编代码: 这里注意到并没有把p作为参数传入到fun,而只是把23这个入参传进去了。也就是说静态成员函数里是没有this的。所以它识别不了a变量。那么这其实和普通的函数没有什么区别了。
虚函数最大的功能就是引入多态,在B1、B2继承A的时候,会重写A中的虚函数fun,这时函数体就不一样了,必定会有不一样的函数入口。B1的fun函数与B2的fun函数入口当然不一样。但是在多态的运用中一个对象A可以指向B1也可以指向B2,在编译的时候,我们不会知道A究竟最后是代表的B1还是B2。所以当编译器翻译虚函数A.fun的时候,不能直接翻译成跳转到某个函数地址。 而是在程序执行到这里的时候,需要读取某个内存来判断应该跳转到哪里。
程序可以用字符串来得到不同的类。这代表着,编译的时候编译器根本不知道p[i]的类型,也就根本没法直接翻译跳转地址。 这里说明一下,多态只能用指针或者应用实现,通过调试程序,我们知道personsize:24、studentsize:32。如果直接用person a=student(),他会给你强制转换类型,但是不会动你的虚函数表。而指针就是直接指向student对象。
回到正题,我们可以查看p[0]指向的内存: 其中内存结构为:
地址内容意义0x000001E5C7378F2090 43 39 3c f7 7f 00 00虚函数表头地址0x000001E5C7378F2814 00 00 00 6d cd cd cd基类中的年龄和性别0x000001E5C7378F3064 00 00 00 cd cd cd cd基类中的票价0x000001E5C7378F3856 34 12 00 cd cd cd cd学生派生类中的ID号其中顺序就是虚函数表头地址占第一个,然后就是基类中的变量,然后就是派生类中的变量。 而虚函数表中就存了当前对象的虚函数的地址: 可以直接在监视器中查看: 在这里可以清楚的看到虚函数表一般是以0结尾,然后这里虚函数表中有两个指针,第二个指向person::run。这是没有被重写的函数。
我们再看看反汇编: 再调用call之前,编译器会判断调用的虚函数是位于基类中的第几个虚函数,来选择偏移量。得到函数地址,进行跳转。同样也传入了this参数。
下次根据已经掌握的类的结构,来探索类继承的原理。