一、变量创建的地方(堆内存与栈内存)
1 栈变量
例如
int a{123} ;
2 堆变量
用new创建的变量是堆变量,例如
int a{123} ;//栈变量a
new int{456};//堆变量没有名字,只有地址
3 栈变量管理堆变量
堆变量没有名字,但是new会把堆变量的地址返回给我们。
这样我们可以通过地址变量来存放这个地址:
int* p = new int{ 456 };
4 指针变量
其实上面的代码中的 地址变量 p 又叫指针变量(pointer)。
二、 栈区 stack memory
void g(void){ //3
int a;
int b;
} //4
void f(void){ //2
int x;
int y;
g();
} //5
int main(){ //1
f();
} //6
上面的函数调用代码执行过程如下:
注:当前函数调用另一个函数的时候,需要先把另一个函数先执行完才会执行当前函数。
也就是先执行最里面的函数,再执行次里面的函数,最后执行最外面的函数。
如下图所示:
1 表示开始调用函数 f()
2 表示函数 f() 开始执行 : 创建栈变量 x , y , 调用 函数 g()
3 表示函数 g() 开始执行, 并分配栈变量 a, b
4 表示函数 g() 执行完毕,接下来 g() 函数所在的栈空间将被清空, a ,b 将不复存在
5 表示函数 f() 执行完毕, 接下来 f() 函数所在的栈空间将被清空,x, y 将不复存在
6 表示函数 main 执行完毕, main 函数所在的栈空间将被清空;同时, 由于 main 函数退出代表整个程序执行完毕,所以操作系统会回收所有资源,包括该为该程序分配的堆空间,以及堆空间上曾经分配的的变量
三、 堆区 dynamic memory
1 指针变量与动态内存
void g(void){ //3
// 下面的new int 返回一个堆空间上的无名变量的地址
// 变量a的类型是指针变量,用于保存变量的地址
int* a = new int;
int b;
} //4
void f(void){ //2
int x;
int y;
g();
} //5
int main(void){ //1
f();
} //6
有了new以后,new得到的整型变量来自堆,下面是有堆参与的程序的执行过程:
3 表示函数 g() 开始执行;并在堆上申请到了一个无名变量,这个无名变量的地址交给 g 函数内的 a 地址变量保存。
4 表示函数 g() 执行完毕,栈上的地址变量 a 释放了, 栈变量 b 也一样不复存在了。此时 堆上分配的无名变量再也没有人知道在哪里了,因为没有变量指向了。这时候就发生了内存泄露。因为没有机会手动释放这个对变量了。
2 内存泄露
内存忘记了释放,会一直占用的情况就叫内存泄露。
new出来的内存为何要delete掉?
试想腾讯的微信服务器,每秒钟都有成千上万的人在使用。如果一个请求到了服务端,就泄露了一个字节。那可想而知,要不了多久,服务器就会有一个机器的内存会因为不断的申请而且不释放最终无内存可申请,这时候操作系统就会杀死微信服务进程(系统异常)。
还有一很多需要长时间运行的程序,比如直播服务器,机顶盒,路由器。等等等。
3 内存释放
void g(void){
int* a = new int;
int b;
delete a;//释放a指向的无名变量
}
void f(void){
int x;
int y;
g();
}
int main(void){
f();
}
delete a; 执行之后,这部分内存就被操作系统收回了,当前程序如果访问,就属于越权。越权是不允许的,操作系统会把当前程序杀掉。调试环境下会抛异常。
使用delete释放的内存,其实是把内存空间归还给了操作系统,这样就可以再次把那块内存分配给其他程序申请使用。
已经归还的内存不可以再访问,如果去读写会发生未定义的行为。
4 地址变量赋值
int* a = new int;
int* b = a;//a 和 b 存储的内容相同,都是堆上的那个无名变量的地址