C++学习笔记04:栈和堆

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次,错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值