同事修改代码,有个函数是返回string的一个引用,函数中有个分支,有一个分支没有调用return返回值,函数类似如下:
#include<iostream>
std::string str("hello");
std::string str1("hello1");
const std::string& get_str()
{
return str;
}
const std::string& get_str1()
{
return str1;
}
const std::string& func(int i)
{
if (i==0){
return get_str();
}else{
get_str1();//没有返回值
}
}
int main()
{
std::cout<<func(0)<<std::endl;
std::cout<<func(1)<<std::endl;
return 0;
}
但是他自己编译、测试没有问题,我自己去验证了一下也没有问题,难道C++这么智能,自动编译优化?不过仔细想下,就应该知道是不可能的,不会做这种多余的事情。于是就研究了一下。
首先上面的代码确实执行后结果符合预期:
pc-csapp06:~/test> ./test
hello
hello1
用gdb翻了一下编译后的汇编代码,又在网上补了一下return的知识,就豁然开朗了。
我是要Suse 11 64位刀片机上测试的。网上知识:函数返回值是使用eax寄存器返回,如果是内置变量,直接在eax中存放值,如果是复杂变量,存放对象地址。不过64位机上使用的是rax,eax是rax的低32位。
汇编:
(gdb) disas /m func
Dump of assembler code for function func(int):
14 const std::string& func(int i)
0x0000000000400aa2 <func(int)+0>: push %rbp 保护rbp——一般都会用rbp来做为栈临时指针,保护上层调用函数的值
0x0000000000400aa3 <func(int)+1>: mov %rsp,%rbp 将栈顶rsp指针给rbp——rsp是栈顶指针,总是指向栈顶。linux,gcc使用的是AT&T格式,和windows的intel汇编不同,mov src dst。
0x0000000000400aa6 <func(int)+4>: sub $0x10,%rsp
0x0000000000400aaa <func(int)+8>: mov %edi,-0x4(%rbp) i放入栈中,不同编译器,使用方式不同,这里用edi传递
15 {
16 if (i==0){
0x0000000000400aad <func(int)+11>: cmpl $0x0,-0x4(%rbp)
0x0000000000400ab1 <func(int)+15>: jne 0x400ac2 <func(int)+32>
17 return get_str();
0x0000000000400ab3 <func(int)+17>: callq 0x400a8c <get_str()>
0x0000000000400ab8 <func(int)+22>: mov %rax,-0x10(%rbp) 将get_str返回值放入栈中
18 }else{
19 get_str1();
0x0000000000400ac2 <func(int)+32>: callq 0x400a97 <get_str1()>
20 }
21 }
0x0000000000400abc <func(int)+26>: mov -0x10(%rbp),%rax 将栈中返回值放入rax中,返回
0x0000000000400ac0 <func(int)+30>: jmp 0x400ac7 <func(int)+37>
0x0000000000400ac7 <func(int)+37>: leaveq
0x0000000000400ac8 <func(int)+38>: retq
可以发现,没有使用return返回的函数,少了将返回值放入栈中的操作,最后返回的直接是rax寄存器的值。但是巧合的是,这个rax,刚好是get_str1函数调用后,用来存放返回值的,所以,最终是没有问题的。
但是,这种写法是不合格的,这次没有问题只是个巧合,如果get_str1后调用了任何一个函数,都会导致结果错误。如:
const std::string& func(int i)
{
if (i==0){
return get_str();
}else{
get_str1();//没有返回值
}
get_str();
}
写成这个样子,结果就有问题了。
还有,现在是返回的一个常量,也就是保证在上层调用的函数使用时,变量是有效的,如果返回的是临时变量,就会有问题了。如:
const std::string get_str()
{
std::string str("hello");
return str;
}
const std::string get_str1()
{
std::string str1("hello1");
return str1;
}
const std::string func(int i)
{
if (i==0){
return get_str();
}else{
get_str1();//没有返回值
}
}
修改成这个样子,则调用get_str1()返回值,是在func函数的作用栈中,在main函数调用func函数时,返回值还是用的这个值,但这个值已经销毁掉了,使用时会segment fault,core掉。