c++内存被分为5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
Stack
存在于作用于的一块内存空间,当你调用函数,函数本身即会形成一块stack用来放置它接收的参数,以及返回地址,在函数body内声明的任何变量,其所使用的内存都源自stack。
离开作用域后,生命周期结束,析构函数自动调用。
Heap
由操作系统提供的一块全局空间,程序可动态分配,通过new创建的的对象会被分配到堆,用完要自己释放。要delet,比如
class Complex {...}
...
{
Complex* p = new Complex; //new的在堆中
...
delete p; //用完要释放
}
在new一个对象的时候,首先分配内存
Complex* pc = new Complex(1,2);
编译器会将其转化为
Complex *pc;
void* mem = operator new( sizeof(Complex) ); //分配内存
pc = static_cast<Complex*>(mem); //转型
pc->Complex::Complex(1,2); //构造函数
在delete时,先调用dtor(析构),在释放memory
String* ps = new String("Hello");
...
delete ps;
编译器转化为:
String::~String(ps); //析构函数
operator delete(ps); //释放内存,内部调用的是free(ps)
Static Object
在作用域中定义时如果在前面家上static,则离开作用域之后仍然存在,只有整个程序结束后,析构函数会被自动调用。对于静态(static)对象,当对象诞生时其构造函数被执行;当程序将结束时其析构函数才被执行,但比全局对象的析构函数早一步执行。
我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义为全局的变量,但定义一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅只受此函数控制)。static 关键字则可以很好的解决这个问题。
static为整个类而非该类的某个对象服务,静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的 main() 函数前的全局数据声明和定义处。static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。它不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存。初始化的时候自动初始化为 0。当程序想要使用全局变量的时候应该先考虑使用 static。
class Account {
public:
static double m_rate; //静态变量声明
static void set_rate(const double& x) {m_rate = x;} //静态函数
};
double Account::m_rate = 8.0; //静态的数据一定要在class外写这么一句,可以不给初值
int main(){
Account::set_rate(5.0); //静态函数没有this指针,所以只能处理静态数据,可以通过类class
// name调用
Account a;
a.set_rate(7.0); //静态函数也可以通过该类的object调用
}
Global Object
定义在任何作用域(也就是{})之外的对象是全局对象,在整个程序结束后析构才会被调用,对于全局对象,程序一开始,其构造函数就先被执行(比程序进入点更早);程序即将结束前其析构函数将被执行。
内存分配释放
新建一个复数,会分配8bit给实部虚部,但是在调试模式下实际上分配给一个复数的有
8+(32+4)+(4*2)=52,调试器模式下在对象数据存放的位置前面还有32,后面还有4个bit,以及最上和最下各4个bit的cookie,有些编译器下还会填补一些bit使其空间成为16的倍数,从52变为64。
如果是release mode,则只有8+(4*2)=16,分别是对象数据8和两个4的cookie。两个cookie的值就是对象占用内存的大小,最后一位0表示没被占用,1表示存了对象,占用了空间
上下cookie的作用是来记录整块区域的大小,以便回收时知道回收多少
对于动态分配所得的数字,如
Complex* p = new Complex[3];
分配的内存是(8*3)+(32+4)+(2*4)+4,最后的4在VC中用来记录数组中元素的个数,这里就是用4bit记录了一个3,release模式下则是(8*3)+(2*4)+4。
array new一定要搭配array delete
String* p = new String[3];
...
delete[] p; //这里一定要加[],这样会唤起3次dtor,如果是delete p;则只唤起1次,错误