研究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指针来调用。
因为同一类的所有对象的成员函数是同一份代码,如果每个对象都保存这样一份代码的话将会浪费极大的空间。所以编译器在编译时只将成员函数的代码保存一份在代码区。然后静态成员函数使用类直接调用,非静态成员函数使用对象来进行调用。
总结
类由数据成员和成员函数构成。但在定义对象时,对象的内存空间中只存放非静态数据成员。
- 非静态数据成员按定义的顺序在对象空间中依次存放,要求各自对齐到自身大小的整数倍。
- 静态数据成员在初始化时存放在全局区,一个类的所有成员都可以对该静态数据成员进行访问。
成员函数的代码跟外部函数和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;
}