首先介绍一下最基本的常识,C++程序占用的内存空间布局情况:
1、栈区(stack):又编译器自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构的栈。
2、堆区(heap):一般是由程序员分配释放,若程序员不释放的话,程序结束时可能由OS回收,值得注意的是他与数据结构的堆是两回事,分配方式倒是类似于数据结构的链表。
3、全局区(static):也叫静态数据内存空间,存储全局变量和静态变量,全局变量和静态变量的存储是放一块的,初始化的全局变量和静态变量放一块区域,没有初始化的在相邻的另一块区域,程序结束后由系统释放。
4、文字常量区:常量字符串就是放在这里,程序结束后由系统释放。
5、程序代码区:存放函数体的二进制代码。
对于C++中的对象来说,举个例子比如在main函数中初始化的对象a,它包含一些数据成员以及函数成员,其中数据成员就存放在属于main函数的栈区上,因为函数代码是类的所有对象公用的,所以成员函数存放在上述程序代码区,只保存一份即可。下边来一个例子:
class A { public: int data1; long data2; char* str; A(); ~A(){delete [] str;} int getdata1()const{return data1;} long getdata2()const{return data2;} void setdata1(int d){data1 = d;} void setdata2(long d){data2 = d;} char * getstr()const{return str;} }; A::A() { data1 = data2 = 0; str = new char[50]; strcpy(str,"hello"); } int main() { A a,b; cout<<"a address:"<<(int*)&a<<endl; cout<<"a.data1 address:"<<(int*)&a.data1<<endl; cout<<"a.data2 address:"<<(int*)&a.data2<<endl; cout<<"a.str address:"<<(int*)a.str<<endl; cout<<endl; cout<<"b address:"<<(int*)&b<<endl; cout<<"b.data1 address:"<<(int*)&b.data1<<endl; cout<<"b.data2 address:"<<(int*)&b.data2<<endl; cout<<"b.str address:"<<(int*)b.str<<endl; cout<<endl; //cout<<&A::getdata1<<endl; printf("A::getdata1 addr=0x%lx\n", &A::getdata1); printf("A::getdata2 addr=0x%lx\n", &A::getdata2); printf("A::setdata1 addr=0x%lx\n", &A::setdata1); printf("A::setdata2 addr=0x%lx\n", &A::setdata2); return 0; }
先说说怎么输出对象的数据成员以及函数成员地址,数据成员地址直接用(int*)&对象名 就可以了,而由于<<并没有对void(__thiscall A::*)()类型重载,编译器将这种类型转换为bool类型,所以无法通过 cout<<&A::getdata1输出成员函数的地址,而静态函数并非__thiscall,<<有对它的重载,因此类的静态函数可以直接用cout输出函数地址。 我们可以用printf输出成员函数的地址,因为它可以接收任意类型的参数,包括__thiscall类型,如上边代码中可以看到。
这个是上述代码的输出结果,运行环境是win7+VC6.0,根据这个结果可以发现对象a跟b的数据成员data1、data2他们的地址都是差不多相邻的,而a和b的str指针值有跟data1、data2的地址相差很远,这是因为动态分配的原因,数据被分配到堆区域中,再看看类A的成员函数地址,跟对象a、b的数据成员也不在一块,实际上是在程序代码区,A的所有的对象共用这些成员函数。看看下边这个图会更加清晰一些。不同的区域用不同的颜色分开,从内存分配布局来看看刚才那个程序。
根据这个图我们可以很容易发现对于C++程序中跟对象相关的内存布局。下面有了一个问题,比如说a对象调用成员函数getdata1即:a.getdata1( );而成员函数内部会去取得数据成员data1,这个过程编译器是怎么完成的呢,我们知道类的任何一个成员函数都包含一个隐藏的this指针参数,这个指针指向了调用此成员函数的对象的地址,有了这个地址,根据上边a的地址就是数据成员的起始地址,加上一定的偏移量,成员函数就可以找到data1或者data2了,所以说再我们写程序a.getdata1( )时,实际上编译器已经把它换做了有this指针参数的全局函数A::getdata1( A* const register this),类的成员函数的本质就是一个C语言中的全局函数。