研究C++对象模型——对象如何在内存存放

研究C++对象模型——对象如何在内存存放

1、不同对象分别存放在哪里

代码:

#include <iostream>
#include <cstring>
using namespace std;

class test
{
    static int count;
    int No;
    char c;
    char *name;
    // 引用等会儿再测试
    // int &r;

public:
    static int *getCountAddr();
    test(char c, const char *name)
    {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
        count++;
        this->No = count;
        this->c = c;

        cout << "construct No." << this->No << " name=" << this->name << endl;
    }
    test(const test &t)
    {
        name = new char[strlen(t.name) + 1];
        strcpy(name, t.name);
        count++;
        No = count;
        c = t.c;
        cout << "copy construct No." << No << " name=" << name << endl;
    }
    ~test()
    {
        // 析构函数,delete申请的空间
        cout << "destruct " << name << endl;
        count--;
        // 为什么出错?
        // delete[] this->name;
        delete[] name;
        name = NULL;
    }
    test *getObjAddr()
    {
        return this;
    }
};
int *test::getCountAddr()
{
    return &count;
}
// 局部对象
void func(test a, test b)
{
    cout << "形参a的地址为:" << a.getObjAddr() << endl;
    cout << "形参b的地址为:" << b.getObjAddr() << endl;
    test obj1('C', "LL");
    test obj2('D', "YY");
    cout << "外部函数中obj1的地址为:" << obj1.getObjAddr() << endl;
    cout << "外部函数中obj2的地址为:" << obj2.getObjAddr() << endl;
    //静态局部对象
    static test staticObj1('G', "gg");
    static test staticObj2('H', "hh");
    cout << "staticObj1的地址为:" << staticObj1.getObjAddr() << endl;
    cout << "staticObj2的地址为:" << staticObj2.getObjAddr() << endl;
}
int test::count = 0;
// 全局对象
test globalObj1('E', "EE");
test globalObj2('F', "FF");

int main()
{
    //全局对象
    cout << "globalObj1的地址为:" << globalObj1.getObjAddr() << endl;
    cout << "globalObj2的地址为:" << globalObj2.getObjAddr() << endl;
    //局部对象
    test obj1('A', "ll");
    test obj2('B', "yy");
    cout << "obj1的地址为:" << obj1.getObjAddr() << endl;
    cout << "obj2的地址为:" << obj2.getObjAddr() << endl;
    //动态对象
    test *dynamicObj1 = new test('I', "ii");
    test *dynamicObj2 = new test('J', "jj");
    cout << "dynamicObj1的地址为:" << dynamicObj1->getObjAddr() << endl;
    cout << "dynamicObj2的地址为:" << dynamicObj2->getObjAddr() << endl;
    delete dynamicObj1;
    delete dynamicObj2;
    //外部函数的对象
    func(obj1, obj2);

    return 0;
}

输出结果:

construct No.1 name=EE
construct No.2 name=FF
globalObj1的地址为:0x408040
globalObj2的地址为:0x408050
construct No.3 name=ll
construct No.4 name=yy
obj1的地址为:0x61fdd0
obj2的地址为:0x61fdc0
construct No.5 name=ii
construct No.6 name=jj
dynamicObj1的地址为:0xea1a00
dynamicObj2的地址为:0xea1a80
destruct ii
destruct jj
copy construct No.5 name=yy
copy construct No.6 name=ll
形参a的地址为:0x61fdf0
形参b的地址为:0x61fde0
construct No.7 name=LL
construct No.8 name=YY
外部函数中obj1的地址为:0x61fd70
外部函数中obj2的地址为:0x61fd60
construct No.9 name=gg
construct No.10 name=hh
staticObj1的地址为:0x408070
staticObj2的地址为:0x408090
destruct YY
destruct LL
destruct ll
destruct yy
destruct yy
destruct ll
destruct hh
destruct gg
destruct FF
destruct EE

全局对象:

地址为0x408040,0x408050,放在静态区(全局变量区)。

main函数中局部对象:

地址为0x61fdd0,0x61fdc0,放在堆栈区

静态局部对象:

地址为0x408070,0x408090,放在静态区(全局变量区)

外部函数func中形参对象:

地址为0x61fdf0,0x61fde0,放在堆栈区

外部函数func中创建的对象:

地址为0x61fd60,0x61fd70,放在堆栈区

动态对象:

地址为0xd84970,0xd849f0,放在堆区

2、对象内部有什么

代码:

#include <iostream>
#include <cstring>
#include <iomanip>
using namespace std;

class test
{
    static int count;
    int No;
    int &r;
    char c;
    char *name;

public:
    static int *getCountAddr();
    static void show()
    {
        cout << "hello" << endl;
    }
    test(int &r, char c, const char *name) : r(r), c(c), name(NULL)
    {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
        count++;
        this->No = count;
        cout << "construct No." << this->No << " name=" << this->name << endl;
    }
    ~test()
    {
        cout << "destruct No." << No << endl;
        count--;
        delete[] name;
    }
    test *getObjAddr()
    {
        return this;
    }
    int *getIntAddr()
    {
        return &No;
    }

    char *getCharAddr()
    {
        return &c;
    }

    char **getCharPointerAddr()
    {
        return &name;
    }
    int *getIntRefAddr()
    {
        return &r;
    }
    void printReference()
    {
        cout << "r=" << r << endl;
    }
    void print()
    {
        ;
    }
};
int *test::getCountAddr()
{
    return &count;
}

void print()
{
    cout << "外部函数" << endl;
}

// 静态成员函数地址输出
typedef void (*staticFunc)();
union
{
    staticFunc f;
    void *addr;
} yesStatic;

typedef int (*mainFunc)();
union
{
    mainFunc f;
    void *addr;
} mFunc;

// 非静态成员函数地址输出
typedef void (test::*Func)();
union
{
    Func f;     // f是Func类型的函数指针
    void *addr; // f也可以看成void*,因此可以打印其值--函数地址
} noStatic;

int test::count = 0;

int main()
{
    int a = 1;
    cout << "a的地址为:" << &a << endl;
    test obj1(a, 'A', "ll");
    test obj2(a, 'B', "yy");
    a = 2;

    yesStatic.f = &test::show;
    cout << "静态函数的地址为:" << hex << noshowbase << yesStatic.addr << endl;

    noStatic.f = &test::print;
    cout << "非静态成员函数的地址为:" << hex << noshowbase << noStatic.addr << endl;

    yesStatic.f = &print;
    cout << "外部函数的地址为:" << hex << noshowbase << yesStatic.addr << endl;

    mFunc.f = &main;
    cout << "main函数的地址为:" << hex << noshowbase << mFunc.addr << endl
         << endl;

    cout << "test::count的地址为:" << test::getCountAddr() << endl;
    cout << "obj1.count的地址为:" << obj1.getCountAddr() << endl;
    cout << "obj2.count的地址为:" << obj2.getCountAddr() << endl
         << endl;

    cout << "obj1的大小为:" << sizeof(obj1) << endl;
    cout << "obj1的占用的地址空间为:" << obj1.getObjAddr() << "-" << (void *)obj1.getObjAddr() + sizeof(obj1) << endl;
    cout << "obj1.No的地址为:" << obj1.getIntAddr() << endl;
    cout << "obj1.r的地址为:" << obj1.getIntRefAddr() << endl;
    cout << "obj1.c的地址为:" << (void *)obj1.getCharAddr() << endl;
    cout << "obj1.name的地址范围为:" << (void *)obj1.getCharPointerAddr() << "-" << (void *)obj1.getCharPointerAddr() + sizeof(char *) << endl;
    obj1.printReference();

    for (unsigned int i = 0; i < sizeof(obj1); i++)
    {
        if (!(i % 8))
            cout << endl;
        cout << hex << setw(8) << right << *(unsigned int *)((char *)obj1.getObjAddr() + i) << " ";
    }
    cout << endl;
    a++;
    for (unsigned int i = 0; i < sizeof(obj2); i++)
    {
        if (!(i % 8))
            cout << endl;
        cout << hex << setw(8) << right << *(unsigned int *)((char *)obj2.getObjAddr() + i) << " ";
    }
    cout << endl;
    // a=3;
    // cout << "obj2的大小为:" << sizeof(obj2) << endl;
    // cout << "obj2的占用的地址空间为:" << obj2.getObjAddr() << "-" << (void *)obj2.getObjAddr() + sizeof(obj2) << endl;
    // cout << "obj2.No的地址为:" << obj2.getIntAddr() << endl;
    // cout << "obj2.c的地址为:" << (void *)obj2.getCharAddr() << endl;
    // cout << "obj2.name的地址范围为:" << (void *)obj2.getCharPointerAddr() << "-" << (void *)obj2.getCharPointerAddr() + sizeof(char *) << endl;
    // obj2.printReference();
    cout << "obj2.r的地址为:" << obj2.getIntRefAddr() << endl;
    //      << endl;
    return 0;
}

输出:

a的地址为:0x61fe04
construct No.1 name=ll
construct No.2 name=yy
静态函数的地址为:0x4034b0
非静态成员函数的地址为:0x4034f0
外部函数的地址为:0x401558
main函数的地址为:0x401589

test::count的地址为:0x408030
obj1.count的地址为:0x408030
obj2.count的地址为:0x408030

obj1的大小为:20
obj1的占用的地址空间为:0x61fde0-0x61fe00
obj1.No的地址为:0x61fde0
obj1.r的地址为:0x61fe04
obj1.c的地址为:0x61fdf0
obj1.name的地址范围为:0x61fdf8-0x61fe00
r=2

       1        0        0        0        0  4000000 fe040000 61fe0400
  61fe04     61fe       61        0        0 41000000   410000     4100
      41        0        0        0        0        0 19000000  5190000
 1051900    10519      105        1        0  8000000    80000      800

       2        0        0        0        0  4000000 fe040000 61fe0400
  61fe04     61fe       61        0        0 42000000 1d420000 401d4200 
  401d42     401d       40        0        0 40000000 19400000  5194000
 1051940    10519      105        1        0  1000000    10000      100
obj2.r的地址为:0x61fe04
destruct No.2
destruct No.1

(1)一般数据成员(int,char,char*)如何存放?

根据结果可以看出,一般数据成员存放的地址为0x61fde0,0x61fdf0,0x61fdf8,这是C++的堆栈区。因为对象是在main函数中静态定义的,所以编译器为该对象在堆栈区分配了一块空间,这块空间里按顺序存放着这些数据成员。类的数据成员被存储在对象的内存空间。
同时我注意到,每个对象所占用的空间只是该对象的数据部分所占用的存储空间,并不包括函数代码所占用的存储空间。

(2)数据成员可以是引用,如何存放?

当打印引用数据成员地址时,结果都是0x61fe04,是我最开始定义的int变量的地址。引用是一个别名,本来不应该为它分配内存空间。但是查看obj1对象的内存空间的内容时,引用数据成员却占据了一定的空间。里面存放的内容应该是指向被引用对象的地址。

所以,当类的数据成员是引用类型时,对象在创建时会分配存放地址的空间,里面存着只想被引用对象的地址,并在构造函数中将引用数据成员绑定到相应的对象。但是,引用数据成员本身并不包含实际的数据值,它只是一个指向被引用对象的指针。

(3)上述数据成员有没有对齐的问题?

有。我设置的数据成员有int(四字节),int&(八字节),char(一字节),char*(八字节),所以对象的大小本来应该为17字节。但是存在对齐的问题,所以每个数据成员都占了八字节,所以总共占了32字节。多余的字节都是补齐的。比如在

       1        0        0        0        0  4000000 fe040000 61fe0400
  61fe04     61fe       61        0        0 41000000   410000     4100
      41        0        0        0        0        0 19000000 eb190000
  eb1900     eb19       eb        0        0  8000000    80000      800

这里面,第一排只有前四位是有效数据位,后四位是补齐的数据。

(4)静态数据成员如何存放?

test::count的地址为:0x408030
obj1.count的地址为:0x408030
obj2.count的地址为:0x408030

如上,根据打印的各对象的静态数据成员count的地址结果可以看出,一个类的静态数据成员共享一片内存空间,并且存放在静态区。

(5)一般成员函数,静态成员函数,外部函数,main函数如何存放?

根据输出结果

静态函数的地址为:0x4034b0
非静态成员函数的地址为:0x4034f0
外部函数的地址为:0x401558
main函数的地址为:0x401589

可以看出函数都存放在代码区。理所当然,外部函数和main函数当然应该存放在代码区。

类的成员函数,无论静态成员函数还是一般成员函数,代码也都存放在代码区。在调用静态成员函数时,因为它是属于类的,所以类可以直接调用。而非静态成员函数是所有的对象都有各自的非静态成员函数,即使所有对象共用代码,但是每个对象是不一样的,比如对本对象的数据成员进行访问操作,所以在调用时使用this指针来调用。

因为同一类的所有对象的成员函数是同一份代码,如果每个对象都保存这样一份代码的话将会浪费极大的空间。所以编译器在编译时只将成员函数的代码保存一份在代码区。然后静态成员函数使用类直接调用,非静态成员函数使用对象来进行调用。

总结

类由数据成员和成员函数构成。但在定义对象时,对象的内存空间中只存放非静态数据成员。

  1. 非静态数据成员按定义的顺序在对象空间中依次存放,要求各自对齐到自身大小的整数倍。
  2. 静态数据成员在初始化时存放在全局区,一个类的所有成员都可以对该静态数据成员进行访问。

成员函数的代码跟外部函数和main函数一起存放在代码区。

遇到的问题

1、浅拷贝问题

外部函数代码如下:

void func(test a, test b)
{
    cout << "形参a的地址为:" << a.getObjAddr() << endl;
    cout << "形参b的地址为:" << b.getObjAddr() << endl;
    test obj1('C', "LL");
    test obj2('D', "YY");
    cout << "外部函数中obj1的地址为:" << obj1.getObjAddr() << endl;
    cout << "外部函数中obj2的地址为:" << obj2.getObjAddr() << endl;
}

test类代码如下:

class test
{
    static int count;
    int No;
    char c;
    char *name;
    // 引用等会儿再测试
    // int &r;

public:
    static int *getCountAddr();
    test(char c, const char *name)
    {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
        count++;
        this->No = count;
        this->c = c;

        cout << "construct No." << this->No << " name=" << this->name << endl;
    }
    ~test()
    {
        //析构函数,delete申请的空间
        cout << "destruct " << name << endl;
        count--;
        //为什么出错?
        //delete[] this->name;
        delete[] name;
        name = NULL;

        
    }
    test *getObjAddr()
    {
        return this;
    }
};

在test类中,我并未编写拷贝构造函数,所以在调用func()函数时,进行了形参传递,此时调用了系统默认的拷贝构造函数,只是简单的将值赋给了形参。但是test类中有char*类型的数据成员,指向堆区的一片内存空间。当func函数执行完后,会自动删除形参,此时调用了析构函数,delete掉了char *指向的内存空间。但是还有一个对象的char *数据成员也指向了该地址。当它被删除时,析构函数会再次删除该空间。因为已经被delete了,所以这时会报错。

解决办法1:将func函数的形参改为引用

void func(test &a, test &b)

解决办法2:自己写一个拷贝构造函数

test(const test &t)
    {
        name = new char[strlen(t.name) + 1];
        strcpy(name, t.name);
        count++;
        No = count;
        c = t.c;
        cout << "copy construct No." << No << " name=" << name << endl;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值