author: hjjdebug
date: 2025年 06月 04日 星期三 11:19:42 CST
descrip: c++返回对象,返回引用,返回指针有什么区别?
$ 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
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
}
可见返回对象的赋值操作被优化没了,直接传递了目标地址.
返回引用还可以看到对目标对象的赋值操作.
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
返回对象, 优化了赋值构造,直接传递了this指针. 在函数内部有外部对象向this对象copy的过程.
返回引用. 在函数内部没有copy的过程, 但在调用处,有赋值构造的过程.
返回指针. 就是返回一个地址, 对象的使用由指针访问.
返回对象,或返回引用都是返回一个地址, 返回对象返回的是目标地址,返回引用返回的是源地址,它们都伴随有源对象向目标对象的数据copy,只是发生的时机不同.
而指针没有目标之说,它指的就是源对象的地址.