当前位置: 首页 > news >正文

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 <stdio.h>
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,%rdi4012b8:	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           # 返回值送rcx4012c5:	48 8b 01             	mov    (%rcx),%rax         # 解引用i1送rax4012c8:	48 8b 51 08          	mov    0x8(%rcx),%rdx      # 解引用i2送rdx4012cc:	48 89 45 e0          	mov    %rax,-0x20(%rbp)    # 保留i14012d0:	48 89 55 e8          	mov    %rdx,-0x18(%rbp)    # 保留i24012d4:	48 8b 41 10          	mov    0x10(%rcx),%rax     # 解引用f送rax4012d8:	48 89 45 f0          	mov    %rax,-0x10(%rbp)    # 保留fMyClass *obj3=getObj3();4012dc:	e8 5d ff ff ff       	callq  40123e <getObj3()>4012e1:	48 89 45 b8          	mov    %rax,-0x48(%rbp)    # 保留返回值到obj3printf("obj1:%d,obj2:%d,obj3:%d\n",obj1.i1,obj2.i1,obj3->i1);4012e5:	48 8b 45 b8          	mov    -0x48(%rbp),%rax4012e9:	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,%esi4012f3:	48 8d 3d 0a 0d 00 00 	lea    0xd0a(%rip),%rdi        # 402004 字符串送rdi,1参数4012fa:	b8 00 00 00 00       	mov    $0x0,%eax4012ff:	e8 6c fd ff ff       	callq  401070 <printf@plt>      #调用printfreturn 0;401304:	b8 00 00 00 00       	mov    $0x0,%eax
}

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

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

MyClass getObj1()
{401196:	f3 0f 1e fa          	endbr64 40119a:	55                   	push   %rbp40119b:	48 89 e5             	mov    %rsp,%rbp40119e:	48 83 ec 20          	sub    $0x20,%rsp4011a2:	48 89 7d e8          	mov    %rdi,-0x18(%rbp)  # 保存this指针4011a6:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax4011ad:	00 00 4011af:	48 89 45 f8          	mov    %rax,-0x8(%rbp)  # 堆栈安全测试代码4011b3:	31 c0                	xor    %eax,%eaxMyClass obj;4011b5:	48 8b 45 e8          	mov    -0x18(%rbp),%rax  #传来的this指针做对象指针来构建对象4011b9:	48 89 c7             	mov    %rax,%rdi4011bc:	e8 5f 01 00 00       	callq  401320 <MyClass::MyClass()> #调用类的构造函数obj.i1=1;4011c1:	48 8b 45 e8          	mov    -0x18(%rbp),%rax  # 把1赋值给对象的i14011c5:	c7 00 01 00 00 00    	movl   $0x1,(%rax)return obj;4011cb:	90                   	nop
}4011cc:	48 8b 45 f8          	mov    -0x8(%rbp),%rax4011d0:	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   %rbp4011eb:	48 89 e5             	mov    %rsp,%rbpstatic 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,%al4011f7:	0f 94 c0             	sete   %al                     # 根据ZF标志位的状态设置al寄存器,如果ZF=1,则al=14011fa:	84 c0                	test   %al,%al4011fc:	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,%eax40120c:	0f 95 c0             	setne  %al         # ZF不等于1,设置al=140120f:	84 c0                	test   %al,%al401211:	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_releaseobj.i1=2;40122b:	c7 05 2b 2e 00 00 02 	movl   $0x2,0x2e2b(%rip)        # 404060 <getObj2()::obj>2送给i1401232:	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    %rbp40123d:	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   %rbp401243:	48 89 e5             	mov    %rsp,%rbpstatic 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,%al40124f:	0f 94 c0             	sete   %al401252:	84 c0                	test   %al,%al401254:	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,%eax401264:	0f 95 c0             	setne  %al401267:	84 c0                	test   %al,%al401269:	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    %rbp401295:	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   %rbp40119b:	48 89 e5             	mov    %rsp,%rbp40119e:	48 83 ec 10          	sub    $0x10,%rsp4011a2:	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,%al4011af:	0f 94 c0             	sete   %al4011b2:	84 c0                	test   %al,%al4011b4:	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,%eax4011c4:	0f 95 c0             	setne  %al4011c7:	84 c0                	test   %al,%al4011c9:	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),%rcx4011f1:	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,只是发生的时机不同.
而指针没有目标之说,它指的就是源对象的地址.

相关文章:

  • spel 多层list嵌套表达式踩坑记
  • Spring AI入门及案例、模型讲解、向量化和RAG等高级应用…
  • 每日Prompt:每天上班的状态
  • 如何在 HTML 中添加按钮
  • Kafka入门-集群基础环境搭建(JDK/Hadoop 部署 + 虚拟机配置 + SSH 免密+Kafka安装启动)
  • 乾元通渠道商中标西藏2024年应急装备采购配置项目
  • SSM 框架核心知识详解(Spring + SpringMVC + MyBatis)
  • Sql Server 中常用语句
  • 数据结构第八章(一) 插入排序
  • 李飞飞World Labs开源革命性Web端3D渲染器Forge!3D高斯溅射技术首次实现全平台流畅运行
  • GPUCUDA 发展编年史:从 3D 渲染到 AI 大模型时代(上)
  • Elasticsearch的审计日志(Audit Logging)介绍
  • 【Android】RV折叠适配器
  • 王道入门50题答案
  • OpenLayers 地图标注之聚合标注
  • 微信小程序前端面经
  • 搭建强化推荐的决策服务架构
  • Quick UI 组件加载到 Axure
  • HashMap中的put方法执行流程(流程图)
  • 搭建nginx的负载均衡
  • 网络互动公司排名/优化seo
  • 地方门户网站源码/app推广策划方案
  • 盘县网站建设/天津做网站的网络公司
  • 手机网站建设深圳/关键词优化推广排名
  • 女生学软件工程很难吗/外贸seo公司
  • 小程序建站平台哪个好/互联网营销师培训机构哪家好