C++的引用
写在前面的话:
rbp一般保存栈基址,rsp一般保存栈顶地址,rax一般用作保存返回值,rdi一般用作传一个参数
rbp,rsp的值进入不同函数后是发生改变的
使用gdb的layout regs方式进行汇编代码的调试
实验的结论是
这样理解:每个函数都有一个返回变量,放在rax寄存器中,而不要用返回值来称呼返回的东西。
type& 引用类型的变量:存放了赋值语句右边的type类型变量的地址,①在赋值时当作type变量使用,②在做形参时传地址(type*),实参是存放这个地址的type&引用变量,③在做返回变量时其实也就是赋值(某变量赋值给返回变量,返回变量赋值给接受变量)。
type&作为函数的返回类型,相当于把rax寄存器作为引用变量使用,存放了return语句右边type类型变量的type*地址,当做type类型变量使用。
使用type变量接收type&返回变量,type&返回变量当做type类型的变量使用,即把返回变量的type值赋值给type接收变量。即把return语句右边的type类型变量赋值给type类型接收变量。
使用type&变量接收type&返回变量,type&返回变量当做type类型的变量使用,接收变量存放返回变量的地址,可以当做type类型变量使用。即体type&接收变量,存放return语句右边的type类型变量的地址,并当做type类型变量使用。
对象实际上就是栈上的几个成员变量,函数都在代码区。
当把返回对象直接赋值给接收对象时,只需要在局部函数中执行一次构造函数
除了调用构造函数外,与普通变量的引用并无什么差别
普通变量的引用类型
main函数中,int赋值给int
1 | int main() |
-0x8(%rbp)存放a的值,-0x4(%rbp)存放b的值。
注:只有ab变量时,没有sub rsp的操作,a,b在栈顶之外,加上c[20]数组后才有sub rsp的操作。
main函数中,int赋值给int&
1 | int main() |
-0x14(%rbp)存放变量a的值,-0x10(%rbp)存放b的值,为变量a的地址。
其它函数中,形参为引用类型
1 | void f(int& c) |
-0x14(%rbp)存放变量a的值,-0x10(%rbp)存放变量b的值,为变量a的地址。变量a的地址存放到rdi,作为参数传给函数f。
-0x8(%rbp)存放变量c的值,为变量a的地址,将该地址上的值变为3。我们看到,函数f()获得了main函数中变量的指针。我们再来看一下void f(int*)的反汇编代码:
1 | void f(int* c) |
和传指针的汇编代码,执行的操作是一样的。
其它函数中,返回类型为引用类型
return 局部变量
1 | int& f(int b) |
报错:
gdb调试查看汇编
main函数,-0x8(%rbp)存放a变量,赋值为1,传给rdi寄存器(edi是rdi的低32位)。
f()中,-0x4(%rbp)存放变量b,b赋值为1+2,将0放入rax寄存器(eax是rax的低32位)。
返回main函数,mov (%rax), %eax的意思是:将以rax寄存器的值指向的内容,放入到rax寄存器(给eax赋值会改变整个rax)中。执行该句后,报错,显然是因为rax里的值是0的缘故:
段错误,访问了不该访问的内存!
结论:不可将局部变量作为引用类型返回!
return 堆上空间的值
将int&类型的返回值赋值给int类型的变量
1 |
|
main函数中,-0x8(%rip)存放了变量a,rdi寄存器保存变量a的值1,进入f()
f()函数中,-0x14(%rbp)存放值1,rdi赋值为4作为malloc的参数,调用malloc函数。
malloc函数返回后,rax已经存放了堆上的地址,-0x8(%rbp)存放int b变量,将堆上地址赋值给int b变量。
lea 0x2(%rax),%eax, 表示将0x3指向的空间的地址,也就是0x3赋值给rax寄存器,将0x3放入int* b指向的空间。
返回值是堆上空间的地址。
返回main函数,%rax存放了堆上空间的地址,-0x4(%rip)存放了变量c,将堆上空间的值放入-0x4(%rip)。
问题:返回main后,没有变量可以指代堆上空间的地址,也就无法释放掉堆上空间
如果再返回main前,释放掉b指向的空间,我们来看看汇编:
在f()中调用free(b),再return *b;
堆上空间的值,已经被free(b)清零了。
最后把值0返回给了c,这不是我们想要的结果!
结论:将堆上空间的值作为引用类型返回给int变量,实际上是先返回堆上空间的地址,再将堆上空间的值赋值给rax寄存器,rax寄存器再赋值给变量。
但是由于不再有变量指代堆上空间,程序将不能释放这个堆上空间,导致这部分堆上空间在程序的剩余执行时间内,不再可用,浪费掉了!这叫做内存泄漏。
而在返回前释放堆上空间,会导致堆上空间清零,虽然返回了堆上空间地址,但赋值给rax寄存器的值是错误的!
将int&类型的返回值赋值给int&类型的变量
1 |
|
从f()返回main后,-0x8(%rbp)存放c变量的值,将堆上空间的值放入了c变量中
c作为堆上空间值的引用,存放了堆上空间的地址,赋值给c时,将值存放到了堆上空间,free(&c)也的确释放了堆上空间。
结论:将堆上空间的值作为引用类型返回给int&变量,从汇编的角度看c存放了*b的地址,从C++的角度看,c和*b一样,是有着堆上空间地址的int变量。可以通过free(&c)释放掉堆上空间。
return 堆上空间的地址
报错,编译不通过。看来使用int&作为返回类型,必须return int类型的变量
返回全局变量
将int&类型的返回值赋值给int类型的变量
1 | int i = 1; |
在f()中,全局变量int i的地址放入了rax寄存器中
将rax存放的地址指向的值,赋值给int类型的接收变量
将int&类型的返回值赋值给int&类型的变量
1 | int i = 1; |
在f()中,同样将全局变量int i的地址放入了rax寄存器中
int&类型的接收变量a,存放了rax寄存器中的地址,在a=3的执行中,表现的像int类型变量一样,将3赋值给了地址指向的空间,我们知道这个地址就是全局变量i的地址,所以它也改变了i。
类的引用类型
main函数中,创建类A的实例
1 | class A |
进入main后,再进入A的构造函数A(),在A中初始化类A的成员变量,A a1创建的对象,成员变量在栈上。A a1 = new A创建的对象,成员变量在堆上。所有函数都存放在内存的代码区中。
main函数中,类A的对象赋值给类A的对象
1 | class A |
对象a1的成员变量存放在0x7fffffffdf80,即-0x10(%rbp)处,对象a2的成员变量存放在0x7fffffffdf84,即-0xc(%rbp)处,对象的赋值,就是把a2的成员变量赋值给a1的成员变量。
A类有两个成员变量也是一样的操作:只不过这次移动了64位
A类有3个成员变量也是一样的操作:只不过分64位,32位各移动了一次
main函数中,A赋值给A&
1 | class A |
-0x10(%rbp)存放对象a1,-0x18(%rbp)存放对象a2,把对象a1的地址,赋值给了对象a2。
成员函数中,形参为引用类型
1 | class A |
main函数中,进入A()构造对象a,将对象a的地址存放到寄存器rdi中,进入成员函数A(A&)
借助传入的对象a的地址,将对象a的值赋值给对象b。
成员函数中,返回类型为类类型
main函数先构造对象b再接受返回变量,f()返回构造好的对象;
1 | //类A有2个整型成员变量 |
进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的两个成员变量值放入rax寄存器中,返回main(),将rax寄存器中的值赋值给对象b。
1 | //类A有3个int型成员变量 |
进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的三个成员变量值拷贝一份(属实迷惑到我了,5 个整型变量又不拷贝一份了),将拷贝后的值放入rax(64位)和rcx(32位)寄存器中,返回main(),将rax和rcx寄存器中的值赋值给对象b。
1 | //类A有一个数量为5的int型数组 |
进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的地址赋值给rax寄存器,返回main(),借助rax,rbx寄存器将对象a的数组值拷贝给对象b的数组。
将类A改成有5个整型成员变量,也是一样的操作,借助rax,rbx寄存器将对象a的值,拷贝给对象b的数组
f()直接返回构造函数
1 | class A |
进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的两个成员变量值放入rax寄存器中,返回main(),将rax寄存器中的值赋值给对象b。与1无区别
main函数直接用f()的返回变量构造对象b
1 | class A |
进入main(),进入f(),进入A()构造对象,返回f(),将对象的两个成员变量值放入rax寄存器中,返回main(),将rax寄存器中的值赋值给对象b。相比1,少调用一次构造函数构造b
main函数直接用f()的返回变量构造对象b,f()直接返回构造函数
1 | class A |
同3,相比1,少调用一次构造函数构造b
成员函数中,返回类型为引用类型
return 局部变量
同样不能返回局部对象,通不过编译
return 堆上对象
接收变量为类型A
1 | class A |
进入f(),new一个8字节(类A实例的大小)的空间,进入A()构造对象,将对象的地址放到rax寄存器中,返回f(),返回main()
借助构造对象的地址,将对象的值,存放到对象b中,然后丢失new的地址。
接受变量为类型A&
将对象的地址,存放到对象b中。
return 全局对象
崩看了,所有分析都与普通变量无二。