c++返回对象,返回引用,返回指针有什么区别?


author: hjjdebug
date: 2025年 06月 04日 星期三 11:19:42 CST
descrip: c++返回对象,返回引用,返回指针有什么区别?


文章目录

  • 1. 测试代码:
  • 2. 反汇编代码分析
    • 2.1: 调用代码分析
    • 2.2: 被调用函数代码分析, 看看它们是怎样返回eax的.
  • 3. 结论:

返回指针,就是返回一个地址,这个容易理解.
返回对象和返回引用呢? 一般来讲,也是返回一个地址, 你想,返回值就存储在eax中,
它在64位机器上也就存储64bits数据, 也就只能存储一个地址.
除非你的对象占据的内存不大于64bits, 例如只有一个int,或者其它由bit组成的位,
能直接返回对象,大于64bits 的对象, 返回对象或者返回引用,就是返回的对象地址.
实测的才最有说服力. 看看它的具体实现细节.

1. 测试代码:

$ cat main.cpp

#include 
class MyClass
{
public:
	int i1;
	int i2;
	float f;
	char *p;
public:
	MyClass(){
		i1=i2=0;
		f=0.0;
		p=NULL;
	}
};
MyClass getObj1()
{
	MyClass obj;
	obj.i1=1;
	return obj;
}

MyClass& getObj2()
{
	static MyClass obj; 
	obj.i1=2;
// warning: reference to local variable ‘obj’ returned [-Wreturn-local-addr]
// 所以我需要吧 obj 声明为static, 这样就没有warning 了
	return obj; 

}

MyClass *getObj3()
{
	static MyClass obj;
	obj.i1=3;
// warning: address of local variable ‘obj’ returned [-Wreturn-local-addr] 
// 所以我需要吧 obj 声明为static, 这样就没有warning 了
	return &obj; 
}

int main() {
	MyClass obj1=getObj1();
	MyClass obj2=getObj2();
	MyClass *obj3=getObj3();
	printf("obj1:%d,obj2:%d,obj3:%d\n",obj1.i1,obj2.i1,obj3->i1);
    return 0;
}

执行结果:
$ ./tt
obj1:1,obj2:2,obj3:3

2. 反汇编代码分析

2.1: 调用代码分析

	MyClass obj1=getObj1();
  4012b1:	48 8d 45 c0          	lea    -0x40(%rbp),%rax     # 直接传递的obj1的地址,做为this指针,显然调用被优化了.
  4012b5:	48 89 c7             	mov    %rax,%rdi
  4012b8:	e8 d9 fe ff ff       	callq  401196 <getObj1()>
	MyClass obj2=getObj2();
  4012bd:	e8 24 ff ff ff       	callq  4011e6 <getObj2()>
  4012c2:	48 89 c1             	mov    %rax,%rcx           # 返回值送rcx
  4012c5:	48 8b 01             	mov    (%rcx),%rax         # 解引用i1送rax
  4012c8:	48 8b 51 08          	mov    0x8(%rcx),%rdx      # 解引用i2送rdx
  4012cc:	48 89 45 e0          	mov    %rax,-0x20(%rbp)    # 保留i1
  4012d0:	48 89 55 e8          	mov    %rdx,-0x18(%rbp)    # 保留i2
  4012d4:	48 8b 41 10          	mov    0x10(%rcx),%rax     # 解引用f送rax
  4012d8:	48 89 45 f0          	mov    %rax,-0x10(%rbp)    # 保留f
	MyClass *obj3=getObj3();
  4012dc:	e8 5d ff ff ff       	callq  40123e <getObj3()>
  4012e1:	48 89 45 b8          	mov    %rax,-0x48(%rbp)    # 保留返回值到obj3
	printf("obj1:%d,obj2:%d,obj3:%d\n",obj1.i1,obj2.i1,obj3->i1);
  4012e5:	48 8b 45 b8          	mov    -0x48(%rbp),%rax
  4012e9:	8b 08                	mov    (%rax),%ecx        # obj3->i1 送ecx,4参数
  4012eb:	8b 55 e0             	mov    -0x20(%rbp),%edx   # obj2.i1 送edx,3参数
  4012ee:	8b 45 c0             	mov    -0x40(%rbp),%eax   # obj1.i1 送esi,2参数
  4012f1:	89 c6                	mov    %eax,%esi
  4012f3:	48 8d 3d 0a 0d 00 00 	lea    0xd0a(%rip),%rdi        # 402004 字符串送rdi,1参数
  4012fa:	b8 00 00 00 00       	mov    $0x0,%eax
  4012ff:	e8 6c fd ff ff       	callq  401070 <printf@plt>      #调用printf
    return 0;
  401304:	b8 00 00 00 00       	mov    $0x0,%eax
}

可见返回对象的赋值操作被优化没了,直接传递了目标地址.
返回引用还可以看到对目标对象的赋值操作.

2.2: 被调用函数代码分析, 看看它们是怎样返回eax的.

MyClass getObj1()
{
  401196:	f3 0f 1e fa          	endbr64 
  40119a:	55                   	push   %rbp
  40119b:	48 89 e5             	mov    %rsp,%rbp
  40119e:	48 83 ec 20          	sub    $0x20,%rsp
  4011a2:	48 89 7d e8          	mov    %rdi,-0x18(%rbp)  # 保存this指针
  4011a6:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  4011ad:	00 00 
  4011af:	48 89 45 f8          	mov    %rax,-0x8(%rbp)  # 堆栈安全测试代码
  4011b3:	31 c0                	xor    %eax,%eax
	MyClass obj;
  4011b5:	48 8b 45 e8          	mov    -0x18(%rbp),%rax  #传来的this指针做对象指针来构建对象
  4011b9:	48 89 c7             	mov    %rax,%rdi
  4011bc:	e8 5f 01 00 00       	callq  401320 <MyClass::MyClass()> #调用类的构造函数
	obj.i1=1;
  4011c1:	48 8b 45 e8          	mov    -0x18(%rbp),%rax  # 把1赋值给对象的i1
  4011c5:	c7 00 01 00 00 00    	movl   $0x1,(%rax)
	return obj;
  4011cb:	90                   	nop
}
  4011cc:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  4011d0:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax     #测试堆栈是否被破坏
  4011d7:	00 00 
  4011d9:	74 05                	je     4011e0 <getObj1()+0x4a> #堆栈正常则正常返回
  4011db:	e8 b0 fe ff ff       	callq  401090 <__stack_chk_fail@plt> #调用堆栈被破坏代码
  4011e0:	48 8b 45 e8          	mov    -0x18(%rbp),%rax  #正常返回,this指针返回(赋值给eax)
  4011e4:	c9                   	leaveq 
  4011e5:	c3                   	retq   


MyClass& getObj2()
{
  4011e6:	f3 0f 1e fa          	endbr64 
  4011ea:	55                   	push   %rbp
  4011eb:	48 89 e5             	mov    %rsp,%rbp
	static MyClass obj; 
  4011ee:	0f b6 05 83 2e 00 00 	movzbl 0x2e83(%rip),%eax        # 404078 <guard variable for getObj2()::obj>
  4011f5:	84 c0                	test   %al,%al
  4011f7:	0f 94 c0             	sete   %al                     # 根据ZF标志位的状态设置al寄存器,如果ZF=1,则al=1
  4011fa:	84 c0                	test   %al,%al
  4011fc:	74 2d                	je     40122b <getObj2()+0x45> # 保护位为0则跳转
  4011fe:	48 8d 3d 73 2e 00 00 	lea    0x2e73(%rip),%rdi        # 404078 <guard variable for getObj2()::obj>
  401205:	e8 96 fe ff ff       	callq  4010a0 <__cxa_guard_acquire@plt>
  40120a:	85 c0                	test   %eax,%eax
  40120c:	0f 95 c0             	setne  %al         # ZF不等于1,设置al=1
  40120f:	84 c0                	test   %al,%al
  401211:	74 18                	je     40122b <getObj2()+0x45>
  401213:	48 8d 3d 46 2e 00 00 	lea    0x2e46(%rip),%rdi        # 404060 <getObj2()::obj>, 对象指针
  40121a:	e8 01 01 00 00       	callq  401320 <MyClass::MyClass()>  # 调用构造函数
  40121f:	48 8d 3d 52 2e 00 00 	lea    0x2e52(%rip),%rdi        # 404078 <guard variable for getObj2()::obj>
  401226:	e8 55 fe ff ff       	callq  401080 <__cxa_guard_release@plt> # 调用 guard_release
	obj.i1=2;
  40122b:	c7 05 2b 2e 00 00 02 	movl   $0x2,0x2e2b(%rip)        # 404060 <getObj2()::obj>2送给i1
  401232:	00 00 00 
// warning: reference to local variable ‘obj’ returned [-Wreturn-local-addr]
// 所以我需要吧 obj 声明为static, 这样就没有warning 了
	return obj; 
  401235:	48 8d 05 24 2e 00 00 	lea    0x2e24(%rip),%rax        # 404060 <getObj2()::obj>  返回对象地址

}
  40123c:	5d                   	pop    %rbp
  40123d:	c3                   	retq   

在 404060 内存区构建了静态变量obj, 调用了构造函数,并将i1赋值为2, 返回对象地址
404078是哨兵保护字节.
0x404000-0x405000都属于可读可写区,包括.got.plt, .data, 及其它例如声明的静态变量.
其后面的可读可写区就是堆了.


000000000040123e <getObj3()>:

MyClass *getObj3()
{
  40123e:	f3 0f 1e fa          	endbr64 
  401242:	55                   	push   %rbp
  401243:	48 89 e5             	mov    %rsp,%rbp
	static MyClass obj;
  401246:	0f b6 05 4b 2e 00 00 	movzbl 0x2e4b(%rip),%eax        # 404098 <guard variable for getObj3()::obj>
  40124d:	84 c0                	test   %al,%al
  40124f:	0f 94 c0             	sete   %al
  401252:	84 c0                	test   %al,%al
  401254:	74 2d                	je     401283 <getObj3()+0x45>
  401256:	48 8d 3d 3b 2e 00 00 	lea    0x2e3b(%rip),%rdi        # 404098 <guard variable for getObj3()::obj>
  40125d:	e8 3e fe ff ff       	callq  4010a0 <__cxa_guard_acquire@plt> # 我跟踪了一下,这些代码是会走到的,
  401262:	85 c0                	test   %eax,%eax
  401264:	0f 95 c0             	setne  %al
  401267:	84 c0                	test   %al,%al
  401269:	74 18                	je     401283 <getObj3()+0x45>
  40126b:	48 8d 3d 0e 2e 00 00 	lea    0x2e0e(%rip),%rdi        # 404080 <getObj3()::obj>
  401272:	e8 a9 00 00 00       	callq  401320 <MyClass::MyClass()>
  401277:	48 8d 3d 1a 2e 00 00 	lea    0x2e1a(%rip),%rdi        # 404098 <guard variable for getObj3()::obj>
  40127e:	e8 fd fd ff ff       	callq  401080 <__cxa_guard_release@plt>
	obj.i1=3;
  401283:	c7 05 f3 2d 00 00 03 	movl   $0x3,0x2df3(%rip)        # 404080 <getObj3()::obj>
  40128a:	00 00 00 
// warning: address of local variable ‘obj’ returned [-Wreturn-local-addr] 
// 所以我需要吧 obj 声明为static, 这样就没有warning 了
	return &obj; 
  40128d:	48 8d 05 ec 2d 00 00 	lea    0x2dec(%rip),%rax        # 404080 <getObj3()::obj>
}
  401294:	5d                   	pop    %rbp
  401295:	c3                   	retq   

与返回引用极其相似,从实现来看,你是分辨不出返回的到底是指针还是引用的, 返回值的解释由调用者去解释.404080 内存区构建了静态变量obj, 调用了构造函数,并将i1赋值为3, 返回对象地址
404098是哨兵保护字节.
我跟踪了一下,哨兵代码是会走到的,即执行流程是获取哨兵,执行构造,释放哨兵. 哨兵应该是检查数据区是否被破坏的保护检测.

我忽然想知道, 返回对象的函数getObj1() 如果也是一个static obj 会如何?
我研究了一下, 调用没有变化, 仍然传递了this指针.
但getObj1() 的实现有了一点变化, 因为要构建static obj, 然后把static_obj 向this指针copy

MyClass getObj1()
{
  401196:	f3 0f 1e fa          	endbr64 
  40119a:	55                   	push   %rbp
  40119b:	48 89 e5             	mov    %rsp,%rbp
  40119e:	48 83 ec 10          	sub    $0x10,%rsp
  4011a2:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)       #保留this 指针
	static MyClass obj;
  4011a6:	0f b6 05 cb 2e 00 00 	movzbl 0x2ecb(%rip),%eax        # 404078 <guard variable for getObj1()::obj>
  4011ad:	84 c0                	test   %al,%al
  4011af:	0f 94 c0             	sete   %al
  4011b2:	84 c0                	test   %al,%al
  4011b4:	74 2d                	je     4011e3 <getObj1()+0x4d>
  4011b6:	48 8d 3d bb 2e 00 00 	lea    0x2ebb(%rip),%rdi        # 404078 <guard variable for getObj1()::obj>
  4011bd:	e8 de fe ff ff       	callq  4010a0 <__cxa_guard_acquire@plt>
  4011c2:	85 c0                	test   %eax,%eax
  4011c4:	0f 95 c0             	setne  %al
  4011c7:	84 c0                	test   %al,%al
  4011c9:	74 18                	je     4011e3 <getObj1()+0x4d>
  4011cb:	48 8d 3d 8e 2e 00 00 	lea    0x2e8e(%rip),%rdi        # 404060 <getObj1()::obj>
  4011d2:	e8 79 01 00 00       	callq  401350 <MyClass::MyClass()>
  4011d7:	48 8d 3d 9a 2e 00 00 	lea    0x2e9a(%rip),%rdi        # 404078 <guard variable for getObj1()::obj>
  4011de:	e8 9d fe ff ff       	callq  401080 <__cxa_guard_release@plt>
	obj.i1=1;
  4011e3:	c7 05 73 2e 00 00 01 	movl   $0x1,0x2e73(%rip)        # 404060 <getObj1()::obj>
  4011ea:	00 00 00 
	return obj;     # 对象成员obj向this copy 的过程.
  4011ed:	48 8b 4d f8          	mov    -0x8(%rbp),%rcx
  4011f1:	48 8b 05 68 2e 00 00 	mov    0x2e68(%rip),%rax        # 404060 <getObj1()::obj>
  4011f8:	48 8b 15 69 2e 00 00 	mov    0x2e69(%rip),%rdx        # 404068 <getObj1()::obj+0x8>
  4011ff:	48 89 01             	mov    %rax,(%rcx)
  401202:	48 89 51 08          	mov    %rdx,0x8(%rcx)
  401206:	48 8b 05 63 2e 00 00 	mov    0x2e63(%rip),%rax        # 404070 <getObj1()::obj+0x10>
  40120d:	48 89 41 10          	mov    %rax,0x10(%rcx)
}
  401211:	48 8b 45 f8          	mov    -0x8(%rbp),%rax          # 返回this 指针
  401215:	c9                   	leaveq 
  401216:	c3                   	retq  

3. 结论:

返回对象, 优化了赋值构造,直接传递了this指针. 在函数内部有外部对象向this对象copy的过程.
返回引用. 在函数内部没有copy的过程, 但在调用处,有赋值构造的过程.
返回指针. 就是返回一个地址, 对象的使用由指针访问.
返回对象,或返回引用都是返回一个地址, 返回对象返回的是目标地址,返回引用返回的是源地址,它们都伴随有源对象向目标对象的数据copy,只是发生的时机不同.
而指针没有目标之说,它指的就是源对象的地址.

你可能感兴趣的:(#,c++,编程,c++,对象,引用,指针)