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,只是发生的时机不同.
而指针没有目标之说,它指的就是源对象的地址.