写在前面的话:

  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
2
3
4
5
6
7
8
int main()
{
int a = 1;
int b = a;
//int c[20] = {0};

return 0;
}

1.png

-0x8(%rbp)存放a的值,-0x4(%rbp)存放b的值。

注:只有ab变量时,没有sub rsp的操作,a,b在栈顶之外,加上c[20]数组后才有sub rsp的操作。

main函数中,int赋值给int&

1
2
3
4
5
6
7
8
int main()
{
int a = 2;
int& b = a;
a = 3;

return 0;
}

2

-0x14(%rbp)存放变量a的值,-0x10(%rbp)存放b的值,为变量a的地址。

其它函数中,形参为引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
void f(int& c)
{
c = 3;
}

int main()
{
int a = 1;
int& b = a;
f(b);

return 0;
}

3

-0x14(%rbp)存放变量a的值,-0x10(%rbp)存放变量b的值,为变量a的地址。变量a的地址存放到rdi,作为参数传给函数f。

4

-0x8(%rbp)存放变量c的值,为变量a的地址,将该地址上的值变为3。我们看到,函数f()获得了main函数中变量的指针。我们再来看一下void f(int*)的反汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
void f(int* c)
{
*c = 3;
}

int main()
{
int a = 1;
int *b = &a;
f(b);

return 0;
}

5

6

和传指针的汇编代码,执行的操作是一样的。

其它函数中,返回类型为引用类型

return 局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int& f(int b)
{
b = b+2;
return b;
}

int main()
{

int a=1;
int c = f(a);

return 0;
}

报错:

image-20211107155329342

image-20211107160841526

gdb调试查看汇编

image-20211107161603255

main函数,-0x8(%rbp)存放a变量,赋值为1,传给rdi寄存器(edi是rdi的低32位)。

image-20211107162450323

f()中,-0x4(%rbp)存放变量b,b赋值为1+2,将0放入rax寄存器(eax是rax的低32位)。

image-20211107162822226

返回main函数,mov (%rax), %eax的意思是:将以rax寄存器的值指向的内容,放入到rax寄存器(给eax赋值会改变整个rax)中。执行该句后,报错,显然是因为rax里的值是0的缘故:

image-20211107162939823

段错误,访问了不该访问的内存!

结论:不可将局部变量作为引用类型返回!


return 堆上空间的值

将int&类型的返回值赋值给int类型的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
int& f(int a)
{
int* b =(int*) malloc(sizeof(int));
*b = a+2;
return *b;
}

int main()
{
int a = 1;
int c = f(a);

return 0;
}

image-20211107170640916

main函数中,-0x8(%rip)存放了变量a,rdi寄存器保存变量a的值1,进入f()

image-20211107171606010

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指向的空间。

返回值是堆上空间的地址。

image-20211107170333419

返回main函数,%rax存放了堆上空间的地址,-0x4(%rip)存放了变量c,将堆上空间的值放入-0x4(%rip)。

问题:返回main后,没有变量可以指代堆上空间的地址,也就无法释放掉堆上空间

如果再返回main前,释放掉b指向的空间,我们来看看汇编:

image-20211107173119809

在f()中调用free(b),再return *b;

image-20211107173330061

堆上空间的值,已经被free(b)清零了。image-20211107173438290

最后把值0返回给了c,这不是我们想要的结果!

结论:将堆上空间的值作为引用类型返回给int变量,实际上是先返回堆上空间的地址,再将堆上空间的值赋值给rax寄存器,rax寄存器再赋值给变量。

但是由于不再有变量指代堆上空间,程序将不能释放这个堆上空间,导致这部分堆上空间在程序的剩余执行时间内,不再可用,浪费掉了!这叫做内存泄漏。

而在返回前释放堆上空间,会导致堆上空间清零,虽然返回了堆上空间地址,但赋值给rax寄存器的值是错误的!


将int&类型的返回值赋值给int&类型的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdlib.h>

int& f(int a)
{
int* b =(int*) malloc(sizeof(int));
*b = a+2;
return *b;
}

int main()
{
int a = 1;
int& c = f(a);
c = 3;
free(&c);

return 0;
}

image-20211107175503044

从f()返回main后,-0x8(%rbp)存放c变量的值,将堆上空间的值放入了c变量中

image-20211107180018364

c作为堆上空间值的引用,存放了堆上空间的地址,赋值给c时,将值存放到了堆上空间,free(&c)也的确释放了堆上空间。

结论:将堆上空间的值作为引用类型返回给int&变量,从汇编的角度看c存放了*b的地址,从C++的角度看,c和*b一样,是有着堆上空间地址的int变量。可以通过free(&c)释放掉堆上空间。


return 堆上空间的地址

image-20211107180713880

报错,编译不通过。看来使用int&作为返回类型,必须return int类型的变量


返回全局变量

将int&类型的返回值赋值给int类型的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
int i = 1;

int& f()
{
i = 2;
return i;
}

int main()
{
int a = f();
return 0;
}

image-20211107194634919

在f()中,全局变量int i的地址放入了rax寄存器中

image-20211107194833278

将rax存放的地址指向的值,赋值给int类型的接收变量


将int&类型的返回值赋值给int&类型的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i = 1;

int& f()
{
i = 2;
return i;
}

int main()
{
int& a = f();
a = 3;
return 0;
}

image-20211107195403884

在f()中,同样将全局变量int i的地址放入了rax寄存器中

image-20211107195518061

int&类型的接收变量a,存放了rax寄存器中的地址,在a=3的执行中,表现的像int类型变量一样,将3赋值给了地址指向的空间,我们知道这个地址就是全局变量i的地址,所以它也改变了i。


类的引用类型

main函数中,创建类A的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A
{
public:
A(int b){a = b;};
public:
int a = 1;
};

int main()
{
A a1(2);

return 0;
}

image-20211107204851274

进入main后,再进入A的构造函数A(),在A中初始化类A的成员变量,A a1创建的对象,成员变量在栈上。A a1 = new A创建的对象,成员变量在堆上。所有函数都存放在内存的代码区中。

main函数中,类A的对象赋值给类A的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{
public:
A(int b){a=b;};
public:
int a = 1;
};

int main()
{
A a1(2);
A a2(3);
a1 = a2;

return 0;
}

image-20211107214336672

对象a1的成员变量存放在0x7fffffffdf80,即-0x10(%rbp)处,对象a2的成员变量存放在0x7fffffffdf84,即-0xc(%rbp)处,对象的赋值,就是把a2的成员变量赋值给a1的成员变量。

A类有两个成员变量也是一样的操作:只不过这次移动了64位

image-20211107215714204

A类有3个成员变量也是一样的操作:只不过分64位,32位各移动了一次

image-20211107220125398

main函数中,A赋值给A&

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A
{
public:
A(int a, int b){
_a = a;
_b = b;
};
public:
int _a = 1;
int _b = 1;
};

int main()
{
A a1(2,2);
A& a2 = a1;

return 0;
}

image-20211107220546454

-0x10(%rbp)存放对象a1,-0x18(%rbp)存放对象a2,把对象a1的地址,赋值给了对象a2。

成员函数中,形参为引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
{
public:
A(int a, int b){
_a = a;
_b = b;
};

A(A& a){
_a = a._a;
_b = a._b;
};
public:
int _a = 1;
int _b = 1;
};

int main()
{
A a(2,2);
A b(a);

return 0;
}

image-20211107221844365

main函数中,进入A()构造对象a,将对象a的地址存放到寄存器rdi中,进入成员函数A(A&)

image-20211107222503560

借助传入的对象a的地址,将对象a的值赋值给对象b。

成员函数中,返回类型为类类型

main函数先构造对象b再接受返回变量,f()返回构造好的对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//类A有2个整型成员变量
class A
{
public:
A(int a, int b)
{
_a = a;
_b = b;
};
public:
int _a;
int _b;
};

A f()
{
A a(2,2);
return a;
}

int main()
{
A b(1,1);
b = f();
return 0;
}

进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的两个成员变量值放入rax寄存器中,返回main(),将rax寄存器中的值赋值给对象b。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//类A有3个int型成员变量
class A
{
public:
A(int a, int b, int c)
{
_a = a;
_b = b;
_c = c;
};
public:
int _a;
int _b;
int _c;
};

A f()
{
A a(2,2,2);
return a;
}

int main()
{
A b(1,1,1);
b = f();
return 0;
}

进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的三个成员变量值拷贝一份(属实迷惑到我了,5 个整型变量又不拷贝一份了),将拷贝后的值放入rax(64位)和rcx(32位)寄存器中,返回main(),将rax和rcx寄存器中的值赋值给对象b。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//类A有一个数量为5的int型数组
int aarray[5] = {2,2,2,2,2};
int barray[5] = {1,1,1,1,1};

class A
{
public:
A(int* a)
{
for (int i=0; i<=4; i++)
_a[i] = a[i];
};
public:
int _a[5];
};

A f()
{
A a(aarray);
return a;
}

int main()
{

A b(barray);
b = f();
return 0;
}

image-20211107233517399

进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的地址赋值给rax寄存器,返回main(),借助rax,rbx寄存器将对象a的数组值拷贝给对象b的数组。

将类A改成有5个整型成员变量,也是一样的操作,借助rax,rbx寄存器将对象a的值,拷贝给对象b的数组

f()直接返回构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
{
public:
A(int a, int b)
{
_a = a;
_b = b;
};
public:
int _a;
int _b;
};

A f()
{
return A(2,2);
}

int main()
{
A b(1,1);
b = f();
return 0;
}

进入main(),进入A()构造对象b,进入f(),进入A()构造对象a,返回f(),将对象a的两个成员变量值放入rax寄存器中,返回main(),将rax寄存器中的值赋值给对象b。与1无区别

main函数直接用f()的返回变量构造对象b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
{
public:
A(int a, int b)
{
_a = a;
_b = b;
};
public:
int _a;
int _b;
};

A f()
{
A a(2,2);
return a;
}

int main()
{
A b = f();
return 0;
}

进入main(),进入f(),进入A()构造对象,返回f(),将对象的两个成员变量值放入rax寄存器中,返回main(),将rax寄存器中的值赋值给对象b。相比1,少调用一次构造函数构造b

main函数直接用f()的返回变量构造对象b,f()直接返回构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A
{
public:
A(int a, int b)
{
_a = a;
_b = b;
};
public:
int _a;
int _b;
};

A f()
{
return A(2,2);
}

int main()
{
A b = f();
return 0;
}

同3,相比1,少调用一次构造函数构造b

成员函数中,返回类型为引用类型

return 局部变量

image-20211108002331594

同样不能返回局部对象,通不过编译

return 堆上对象

接收变量为类型A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A
{
public:
A(int a, int b)
{
_a = a;
_b = b;
};

public:
int _a;
int _b;
};

A& f()
{
return *(new A(1,1));
};

int main()
{
A b = f();

return 0;
}

image-20211108003228999

进入f(),new一个8字节(类A实例的大小)的空间,进入A()构造对象,将对象的地址放到rax寄存器中,返回f(),返回main()

image-20211108003633970

借助构造对象的地址,将对象的值,存放到对象b中,然后丢失new的地址

接受变量为类型A&

image-20211108003907226

将对象的地址,存放到对象b中。

return 全局对象

崩看了,所有分析都与普通变量无二。