栈(stack) VS. 堆(heap)
栈
- 由系统自动管理,以执行函数为单位
- 空间大小编译时确定(参数+局部变量)
- 函数执行时,系统自动匹配一个stack
- 函数执行结束,系统立即自动回收stack
反例–悬浮指针:
1
2
3
4
5
6 1myclass * func(){
2 myclass c(10);
3 return &c; //返回栈对象的地址!
4}
5
6
1
2
3
4
5
6
7
8
9 1myclass func(){
2 myclass c(10);
3 aclass a(100);
4
5 c.pa = &a;
6 return c;//返回c会调用拷贝构造函数,aclass是栈对象,c.pa会发生悬空指针
7}
8
9
指向栈对象的指针是非常危险的!!!
还有一种危险操作是用堆对象的指针指向栈对象。
堆
-
在c++中由程序员手动控制
-
手动分配new和malloc
-
手动释放delete和free
-
具有全局性,总体无大小限制
-
容易造成内存泄漏
1
2
3
4
5
6
7
8 1myclass * func(){
2 myclass * pa = new myclass();
3 return pa;
4}
5
6myclass * p = func(); //接收指针的人不一定知道要delete掉这个指针
7
8
返回值尽量不要返回指针:
- 返回栈对象指针是错误
- 返回堆对象指针:接收指针的人要负责delete掉这个指针,这和谁分配谁释放相悖。
堆对象的空间分析
堆对象:栈上存储指针,堆上存储真正的对象
关键点:要会画内存模型
栈对象的空间分析
栈对象:对象内存直接存储于栈空间
变量模型与使用
-
三种变量模型
-
对象 myclass c;
- 指针 myclass * pc; (不要和解引用混淆c=*pc;)
- 引用 myclass & c2 = c; (不要和取地址混淆pc=&c;)
-
三种使用场景
-
声明对象
- 传参
- 返回值
在堆上的变量要么是指针表示要么是引用表示:
1
2
3
4 1myclass * pc2 = new myclass();//指针表示堆对象
2myclass& c3 = *pc2; //引用表示堆对象
3
4
传参:
by value / by pointer/ by reference
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1void func1(myclass c){ //对象往往很大,值传递会发生拷贝
2 //对象的大小依赖于其含有的字段、虚函数、对齐等
3}
4
5void func2(myclass *pc){
6// 传指针从拷贝成本上是可以的。但是也有它的问题。
7// 一般不建议传指针。
8// 1. 栈指针和堆指针看不出来
9// 2. 要不要在函数里delete掉(栈指针delete会出现行为未定义;堆对象也不应该是你来delete,谁分配谁释放)
10}
11
12void func3(myclass& mc){ //
13//传引用相对安全
14}
15
16
例子:谁分配谁释放:
1
2
3
4
5 1myclass * pc = new myclass();
2func2(pc);
3delete pc;
4
5
反例:
1
2
3
4
5
6 1void func3(myclass & mc){
2 myclass *p = &mc;
3 delete p;//有危险,mc并不是函数内分配的,不应该在函数内释放!
4}
5
6
引用在行为上和值传递不完全一样(引用在函数内部修改,函数外部也会变),怎么办?
const引用–值传递的替代品!
但凡这个类型比int大,传引用都有价值。
1
2
3
4
5
6 1myclass c1;
2func(c1); //调用拷贝构造
3func2(&c1); //没有调用拷贝构造
4func3(c1); //没有调用拷贝构造
5
6
例子–返回值(两个方式):
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1myclass func1(){
2 myclass c1;
3 return c1; //方式1
4 myclass * pc2 = new myclass();
5 return *pc2; //方式2,出现内存泄漏
6}
7
8myclass c = func1(); //c是一个新的对象,是一个拷贝
9//pc2被丢掉了,出现了内存泄漏
10//内存泄漏是发生在进程范围内的
11//自己运行看不出来,因为运行一会就关了
12//在服务器上,一个用户运行泄漏一点(比如40byte),几天几个月服务器就会死掉
13
14
例子–返回指针(两种方式):
1
2
3
4
5
6
7
8 1myclass* func2(){
2 myclass c1;
3 return &c1; //方式1,返回栈对象的地址,悬空指针
4 myclass *pc2 = new myclass();
5 return pc2; //方式2,有内存泄漏的危险,违反谁分配谁释放
6}
7
8
慎用返回指针!
例子–返回引用:
1
2
3
4
5
6
7
8
9
10
11
12 1myclass& func3(){
2 myclass c1;
3 return c1;//返回栈对象引用,错误
4 myclass *pc2 = new myclass();
5 return *pc2;//违反谁分配谁释放
6}
7
8myclass c4 = func3();//发生拷贝构造,pc2指向的堆对象会内存释放
9myclass& c4 = func3(); //语法正确,但是极少这么使用
10delete &c4;
11
12
*this是可以返回的,因为其1. 生存周期很长;2. this怎么传进来怎么返回去,合理
1
2
3
4
5 1myclass& func4(myclass& c){
2 return c;
3}
4
5
能不能只有栈没有堆?不能
- 栈是编译时就要确定的,具有静态特征;
- 堆具有全局性,具有灵活性,读取1000000个数据
能不能只有堆没有栈?
- 没有栈就没有函数了,所有函数的状态、操作数,变量都没了。
- 堆是需要栈指针来指向的。