c++内存中的对象布局分析

时间:2022-08-30 17:59:41

在32 位linux 或32的 Windows xp3 编译main.cpp:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

using namespace std;

class demo
{
public:
    int a;
    int b;
};

class methodDemo
{
public:
    void fun()  {};
    void fun2() {};
    void getFunAddress();
public:
    int member;
//private:
    //int private_member;
};

class subMethodDemo : public methodDemo
{
public:
    int subMember;
};

class VisualDemo
{
public:
    virtual void needDoSomething()
    {
        cout << "===================Do First Visual Method======================" << endl;
    };
    virtual void needDoSomething2()
    {
        cout << "===================Do Second Visual Method======================" << endl;
    };

public:
    int visualMember;
};

class SubVisualDemo : public VisualDemo
{
public:
    int subVisualMember;
};

void methodDemo::getFunAddress()
{
    void (methodDemo::*ptrnonstatic)() = &methodDemo::fun;
    printf("methodDemo的fun()方法的地址== %p\n",ptrnonstatic);// 方法的 地址不在对象首地址的偏移范围之内

    void (methodDemo::*pFun2)() = &methodDemo::fun2;
    printf("methodDemo的fun2()方法的地址== %p\n",pFun2);

}

int main()
{
    printf("----------------------无继承层次下对象和实例变量的地址--------------------\n");
    demo mydemo1;
    demo mydemo2;
    demo mydemo3;
    demo mydemo4;
    printf("demo1的地址 == %p\n",&mydemo1);
    printf("demo1 成员a的地址 == %p\n",&(mydemo1.a)); //成员a的地址就是对象的首地址

    printf("demo2的地址 == %p\n",&mydemo2);
    printf("demo2 成员a的地址 == %p\n",&(mydemo2.a));//成员a的地址就是对象的首地址

    printf("demo3的地址 == %p\n",&mydemo3);
    printf("demo3 成员a的地址 == %p\n",&(mydemo3.a));//成员a的地址就是对象的首地址

    printf("demo4的地址 == %p\n",&mydemo4);
    printf("demo4 成员a的地址 == %p\n",&(mydemo4.a));//成员a的地址就是对象的首地址

    printf("\n\n\n");

    printf("---------无继承层次下对象、实例变量、成员函数的地址--------------------\n");
    methodDemo myMethodDemo;
    printf("myMethodDemo的地址 ==  %p\n",&myMethodDemo);
    printf("myMethodDemo的member成员地址 = %p\n",&(myMethodDemo.member)); //成员a的地址就是对象的首地址
    myMethodDemo.getFunAddress();
    printf("\n\n\n");

    printf("---无虚函数情况下派生类对象、父类中的实例变量、子类中的实例变量的地址---\n");
    subMethodDemo mySubMethodDemo;
    printf("mySubMethodDemo的地址 == %p\n",&mySubMethodDemo);
    printf("mySubMethodDemo的父类成员member的地址 == %p\n",&(mySubMethodDemo.member));
    printf("mySubMethodDemo的成员subMember的地址 == %p\n",&(mySubMethodDemo.subMember));
    printf("\n\n\n");

    printf("---有虚函数情况下父类对象、父类中的实例变量、子类中的实例变量的地址---\n");
    VisualDemo visualdemo;
    printf("visualdemo的地址 ==  %p\n",&visualdemo);
    printf("visualdemo的成员visualMember地址 ==  %p\n",&(visualdemo.visualMember));
    printf("\n\n\n");

    printf("---有虚函数情况下子类对象、父类中的实例变量、子类中的实例变量的地址---\n");
    SubVisualDemo subVisualDemo;
    printf("subVisualDemo的地址 == %p\n",&subVisualDemo);
    printf("subVisualDemo的父类成员visualMember的地址 == %p\n",&(subVisualDemo.visualMember));
    printf("subVisualDemo的成员subVisualMember的地址 == %p\n",&(subVisualDemo.subVisualMember));
    printf("\n\n\n");

    printf("---虚函数表的使用和学习-----------------------------------------------\n");
    typedef void(*VisualFun)(void);
    VisualFun visualFun = NULL;

    visualFun = (VisualFun)*((int*)*(int*)(&subVisualDemo) + 0);
    printf("第一个虚函数needDoSomething地址 == %p\n",visualFun);
    visualFun();

    visualFun = (VisualFun)*((int*)*(int*)(&subVisualDemo) + 1);
    printf("第二个虚函数needDoSomething2地址 == %p\n",visualFun);
    visualFun();
    return 0;
}

以下是 程序运行的输出:
----------------------无继承层次下对象和成员的地址--------------------
demo1的地址 == 0xbff6f06c
demo1 成员a的地址 == 0xbff6f06c
demo2的地址 == 0xbff6f068
demo2 成员a的地址 == 0xbff6f068
demo3的地址 == 0xbff6f064
demo3 成员a的地址 == 0xbff6f064
demo4的地址 == 0xbff6f060
demo4 成员a的地址 == 0xbff6f060
分析:如果类中没有虚函数,则实例对象中的实例变量的首地址既是实例对象的地址。如果有多个实例变量按照定义时的顺序来确定哪个是实例对象的地址。


---------无继承层次下对象、实例变量、成员函数的地址--------------------
myMethodDemo的地址 ==  0xbff6f05c
myMethodDemo的member成员地址 = 0xbff6f05c
methodDemo的fun()方法的地址== 0x8048c0c
methodDemo的fun2()方法的地址== 0x8048c06
分析:实例方法地址不在实例对象的起始地址。这个地址可能是进程中的代码段,所以地址比实例对象首地址低。

---无虚函数情况下派生类对象、父类中的实例变量、子类中的实例变量的地址---
mySubMethodDemo的地址 == 0xbff6f050
mySubMethodDemo的父类成员member的地址 == 0xbff6f050
mySubMethodDemo的成员subMember的地址 == 0xbff6f054
分析:派生类中拥有父类的数据成员;即使父类中的数据成员是private或protected。数据成员在内存中的排列顺序是:父类数据成员->派生类数据成员。所以父类成员的首地址就是派生类对象的地址。


---有虚函数情况下父类对象、父类中的实例变量、子类中的实例变量的地址---
visualdemo的地址 ==  0xbff6f040
visualdemo的成员visualMember地址 ==  0xbff6f044
分析:如果类中有虚函数,则实例对象的首地址是虚函数表地址。


---有虚函数情况下子类对象、父类中的实例变量、子类中的实例变量的地址---
subVisualDemo的地址 == 0xbff6f030
subVisualDemo的父类成员visualMember的地址 == 0xbff6f034
subVisualDemo的成员subVisualMember的地址 == 0xbff6f038
分析:派生类继承自有虚函数的父类,首地址是虚函数表地址,之后是父类数据成员的地址,之后是派生类自己的数据成员的地址。


---虚函数表的使用和学习-----------------------------------------------
第一个虚函数needDoSomething地址 == 0x8048ca2
===================Do First Visual Method======================
第二个虚函数needDoSomething2地址 == 0x8048c74
===================Do Second Visual Method======================
分析:虚函数表的理解参见《C++虚函数表解析.pdf》