【C++】C++函数需要有返回值,但非全分支return(RVO)
今天在review
以前的代码的时候,遇到了一个比较奇怪的现象,函数的有返回值,但只在if
后面有return
,else
后面忘写了。但这个版本的代码已经调试验证通过了,没有问题的,这就很怪异。
考验一道题
下面这道题Print
的内容是什么?
# include <iostream>class Test {public:Test (int xx, std::string yy) {x = xx;y = yy;}int x;std::string y;void Print() {std::cout << "x : " << x << " y : " << y << std::endl;}
};Test fun(bool flag) {Test t (0, "0");if (flag) {t.x = 1;t.y = "1";return t;} else {t.x = -1;t.y = "-1";}
}int main(int argc, char const *argv[])
{Test t = fun(false);t.Print();return 0;
}
编译并运行:
g++ test.cpp -o test
./test
可能你也能猜到最终的结果:
x : -1 y : -1
也可能会疑问,在fun
函数的else
语句中并没有提供返回值啊?为什么还能有输出么?或者,会问函数的return
语句不全,不是应该报错么?
函数的返回值
没有return语句
如果一个函数需要有返回值,但是没有return
语句,这会出现什么?
# include <iostream>class Test {public:Test (int xx, std::string yy) {x = xx;y = yy;}int x;std::string y;void Print() {std::cout << "x : " << x << " y : " << y << std::endl;}
};Test fun(bool flag) {Test t (0, "0");if (flag) {t.x = 1;t.y = "1";} else {t.x = -1;t.y = "-1";}
}int main(int argc, char const *argv[])
{Test t = fun(false);t.Print();return 0;
}
编译并运行:
g++ test.cpp -o test
./test
编译没有任何问题,但是在运行的时候出现了错误:
x : 2 y : H��H9�u�H�[]A\A]A^A_Ðf.���H�H��x : y : 01-1;d����h����^���h���������U����j�������� <���Hh��������@zRx�����*zRx�$
*** Error in `./test': munmap_chunk(): invalid pointer: 0x000000000040112d ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f4648daf7e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7f4648dbc698]
./test[0x4010d0]
./test[0x400f56]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f4648d58830]
./test[0x400d09]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:01 263039 /home/yngzmiao/test/test/test
00601000-00602000 r--p 00001000 08:01 263039 /home/yngzmiao/test/test/test
00602000-00603000 rw-p 00002000 08:01 263039 /home/yngzmiao/test/test/test
01656000-01688000 rw-p 00000000 00:00 0 [heap]
7f4648a2f000-7f4648b37000 r-xp 00000000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4648b37000-7f4648d36000 ---p 00108000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4648d36000-7f4648d37000 r--p 00107000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4648d37000-7f4648d38000 rw-p 00108000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4648d38000-7f4648ef8000 r-xp 00000000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f4648ef8000-7f46490f8000 ---p 001c0000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f46490f8000-7f46490fc000 r--p 001c0000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f46490fc000-7f46490fe000 rw-p 001c4000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f46490fe000-7f4649102000 rw-p 00000000 00:00 0
7f4649102000-7f4649118000 r-xp 00000000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f4649118000-7f4649317000 ---p 00016000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f4649317000-7f4649318000 rw-p 00015000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f4649318000-7f464948a000 r-xp 00000000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f464948a000-7f464968a000 ---p 00172000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f464968a000-7f4649694000 r--p 00172000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f4649694000-7f4649696000 rw-p 0017c000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f4649696000-7f464969a000 rw-p 00000000 00:00 0
7f464969a000-7f46496c0000 r-xp 00000000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7f464989d000-7f46498a3000 rw-p 00000000 00:00 0
7f46498be000-7f46498bf000 rw-p 00000000 00:00 0
7f46498bf000-7f46498c0000 r--p 00025000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7f46498c0000-7f46498c1000 rw-p 00026000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7f46498c1000-7f46498c2000 rw-p 00000000 00:00 0
7ffc906a9000-7ffc906ca000 rw-p 00000000 00:00 0 [stack]
7ffc90796000-7ffc90799000 r--p 00000000 00:00 0 [vvar]
7ffc90799000-7ffc9079b000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
已放弃 (核心已转储)
也就是说,如果一个函数声明或定义了有返回值,那么就必须要有return语句!
那么,如果出现了分支语句,必须保证所有的出口都必须有return
么?也就是本文开始提出的那个问题。当然,答案也已经被证明出来了,并不需要。
非全出口return语句
在正式解释之前,再看一个例子:
# include <iostream>class Test {public:Test (int xx, std::string yy) {x = xx;y = yy;}int x;std::string y;void Print() {std::cout << "x : " << x << " y : " << y << std::endl;}
};Test fun(bool flag) {if (flag) {Test t (0, "0");t.x = 1;t.y = "1";return t;} else {Test t (0, "0");t.x = -1;t.y = "-1";}
}int main(int argc, char const *argv[])
{Test t = fun(false);t.Print();return 0;
}
编译并运行:
g++ test.cpp -o test
./test
编译没有任何问题,但是在运行的时候出现了错误:
x : 2 y : H��H9�u�H�[]A\A]A^A_Ðf.���H�H��x : y : 01-1;l����h����^����^���������"���8�������(
���P*���ph��� ����hzRx�����*zRx*** Error in `./test': munmap_chunk(): invalid pointer: 0x00000000004012ad ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f4ad9fd07e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7f4ad9fdd698]
./test[0x40121e]
./test[0x4010a3]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f4ad9f79830]
./test[0x400d89]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:01 262629 /home/yngzmiao/test/test/test
00601000-00602000 r--p 00001000 08:01 262629 /home/yngzmiao/test/test/test
00602000-00603000 rw-p 00002000 08:01 262629 /home/yngzmiao/test/test/test
011d1000-01203000 rw-p 00000000 00:00 0 [heap]
7f4ad9c50000-7f4ad9d58000 r-xp 00000000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4ad9d58000-7f4ad9f57000 ---p 00108000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4ad9f57000-7f4ad9f58000 r--p 00107000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4ad9f58000-7f4ad9f59000 rw-p 00108000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7f4ad9f59000-7f4ada119000 r-xp 00000000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f4ada119000-7f4ada319000 ---p 001c0000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f4ada319000-7f4ada31d000 r--p 001c0000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f4ada31d000-7f4ada31f000 rw-p 001c4000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7f4ada31f000-7f4ada323000 rw-p 00000000 00:00 0
7f4ada323000-7f4ada339000 r-xp 00000000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f4ada339000-7f4ada538000 ---p 00016000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f4ada538000-7f4ada539000 rw-p 00015000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f4ada539000-7f4ada6ab000 r-xp 00000000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f4ada6ab000-7f4ada8ab000 ---p 00172000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f4ada8ab000-7f4ada8b5000 r--p 00172000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f4ada8b5000-7f4ada8b7000 rw-p 0017c000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f4ada8b7000-7f4ada8bb000 rw-p 00000000 00:00 0
7f4ada8bb000-7f4ada8e1000 r-xp 00000000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7f4adaabe000-7f4adaac4000 rw-p 00000000 00:00 0
7f4adaadf000-7f4adaae0000 rw-p 00000000 00:00 0
7f4adaae0000-7f4adaae1000 r--p 00025000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7f4adaae1000-7f4adaae2000 rw-p 00026000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7f4adaae2000-7f4adaae3000 rw-p 00000000 00:00 0
7ffeaa7a8000-7ffeaa7c9000 rw-p 00000000 00:00 0 [stack]
7ffeaa7fa000-7ffeaa7fd000 r--p 00000000 00:00 0 [vvar]
7ffeaa7fd000-7ffeaa7ff000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
已放弃 (核心已转储)
和第二例没有return
的语句一样,也出现了类似的错误。但这段程序是有return
语句的啊!
细细分析代码的区别,其实可以发现,分支中没有return分支的函数出口,好像有一个默认的返回值,就是有return语句分支的返回值变量的所在地址!
第一例中,else
语句尽管没有return
语句,但是返回了一个与if
语句中return
的同一个变量地址的内容;而第三例中,else
语句中并没有if
语句中return
的同一变量地址的内容(栈被释放了)!
当然,这些都是通过区分代码得到的浅显的认知,但这究竟是什么原因呢?
编译器的锅(RVO)
一般来说,为了从一个函数得到运行结果,常规的途径有两个:通过返回值和通过传入函数的引用或指针。
当通过返回值的时候,如果是类的对象或指针的时候,需要注意拷贝构造函数!
拷贝构造函数通常有三种使用场景:
- 用一个对象初始化另一个对象;
- 函数的形参与实参结合时;
- 函数返回时,栈上对象复制到返回值中时。
也就是说,当函数返回时,需要将栈上的对象通过拷贝构造函数,复制到调用该函数的返回值中去。即,把return t
的t
复制到Test t = fun(false)
的t
中去。
而RVO(C++的返回值优化)
是指:C++标准允许一种(编译器)实现省略创建一个只是为了初始化另一个同类型对象的临时对象。基本手段是直接将返回的对象构造在调用者栈帧上,这样调用者就可以直接访问这个对象而不必复制。指定这个参数(-fno-elide-constructors
)将关闭这种优化,强制G++
在所有情况下调用拷贝构造函数。
现在就清楚了,当函数有一个return
后,就会将该return
的对象直接构造在调用者栈上,就不需要走return
的拷贝构造函数。
当然可以试验一下,利用-fno-elide-constructors
关闭优化:
编译并运行:
g++ -fno-elide-constructors test.cpp -o test
./test
编译没有任何问题,但是在运行的时候出现了错误:
*** Error in `./test': munmap_chunk(): invalid pointer: 0x00000000004012bd ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fdcb3df47e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7fdcb3e01698]
./test[0x401228]
./test[0x40105d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fdcb3d9d830]
./test[0x400d89]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:01 262629 /home/yngzmiao/test/test/test
00601000-00602000 r--p 00001000 08:01 262629 /home/yngzmiao/test/test/test
00602000-00603000 rw-p 00002000 08:01 262629 /home/yngzmiao/test/test/test
00f88000-00fba000 rw-p 00000000 00:00 0 [heap]
7fdcb3a74000-7fdcb3b7c000 r-xp 00000000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7fdcb3b7c000-7fdcb3d7b000 ---p 00108000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7fdcb3d7b000-7fdcb3d7c000 r--p 00107000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7fdcb3d7c000-7fdcb3d7d000 rw-p 00108000 08:01 5247874 /lib/x86_64-linux-gnu/libm-2.23.so
7fdcb3d7d000-7fdcb3f3d000 r-xp 00000000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7fdcb3f3d000-7fdcb413d000 ---p 001c0000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7fdcb413d000-7fdcb4141000 r--p 001c0000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7fdcb4141000-7fdcb4143000 rw-p 001c4000 08:01 5247804 /lib/x86_64-linux-gnu/libc-2.23.so
7fdcb4143000-7fdcb4147000 rw-p 00000000 00:00 0
7fdcb4147000-7fdcb415d000 r-xp 00000000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdcb415d000-7fdcb435c000 ---p 00016000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdcb435c000-7fdcb435d000 rw-p 00015000 08:01 5247842 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdcb435d000-7fdcb44cf000 r-xp 00000000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fdcb44cf000-7fdcb46cf000 ---p 00172000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fdcb46cf000-7fdcb46d9000 r--p 00172000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fdcb46d9000-7fdcb46db000 rw-p 0017c000 08:01 2099340 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7fdcb46db000-7fdcb46df000 rw-p 00000000 00:00 0
7fdcb46df000-7fdcb4705000 r-xp 00000000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7fdcb48e2000-7fdcb48e8000 rw-p 00000000 00:00 0
7fdcb4903000-7fdcb4904000 rw-p 00000000 00:00 0
7fdcb4904000-7fdcb4905000 r--p 00025000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7fdcb4905000-7fdcb4906000 rw-p 00026000 08:01 5247776 /lib/x86_64-linux-gnu/ld-2.23.so
7fdcb4906000-7fdcb4907000 rw-p 00000000 00:00 0
7ffe40205000-7ffe40226000 rw-p 00000000 00:00 0 [stack]
7ffe40320000-7ffe40323000 r--p 00000000 00:00 0 [vvar]
7ffe40323000-7ffe40325000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
已放弃 (核心已转储)
也就是说,确实是因为RVO(C++的返回值优化)
的原因。
当然,保证每个分支出口都有return才是最重要的!
相关阅读
【C++踩坑】说说g++的-fno-elide-constructors参数
C++的返回值优化(RVO,Return Value Optimization)
【C++】C++函数需要有返回值,但非全分支return(RVO)相关推荐
- wpf 窗口的返回值_如何:获取页函数的返回值
如何:获取页函数的返回值How to: Get the Return Value of a Page Function 03/30/2017 本文内容 本示例显示如何获取页函数的返回值.This ex ...
- 决定c++语言中函数的返回值类型的是,全国2009年10月高等教育自学考试C++程序设计试题及部分参考答案...
全国2009年10月高等教育自学考试 C++程序设计试题 课程代码:04737 一.单项选择题(本大题共20小题,每小题1分,共20分) 在每小题列出的四个备选项中只有一个是符合题目要求的,请将其代码 ...
- 一木.溪桥学Python-09:函数的返回值、函数的作用域、作用域的优先级、递归函数、内置文件中常用方法、匿名函数lambda、高阶函数
一木.溪桥 在Logic Education跟Amy学Python 12期:Python基础课 一木.溪桥学Python-09:函数的返回值.作用域.作用域的优先级.递归函数.内置文件中常用方法.匿名 ...
- 站长在线Python精讲:Python中函数的返回值
欢迎你来到站长在线的站长学堂学习Python知识,本文学习的是<Python中函数的返回值>.本文的主要内容有:函数的返回值的含义.函数设置返回值的作用.return语句位置与多条 ret ...
- 非阻塞模式下 SEND 和 RECV 函数的返回值总结
send 和 recv 函数的各种返回值意义: 返回值 n 返回值含义 大于 0 成功发送 n 个字节 0 对端关闭连接 小于 0( -1) 出错或者被信号中断或者对端 TCP 窗口太小数据发不出去( ...
- 【C 语言】字符串模型 ( 两头堵模型 | 将 两头堵模型 抽象成业务模块函数 | 形参返回值 | 函数返回值 | 形参指针判空 | 形参返回值操作 )
文章目录 一.将 两头堵模型 抽象成业务模块函数 二.完整代码示例 一.将 两头堵模型 抽象成业务模块函数 将 两头堵模型 抽象成业务模块函数 相关要点 : 形参返回值 : 函数的返回值 , 一般使用 ...
- python函数的返回值是返回引用吗_python-函数(上):函数返回值、函数调用、前向引用...
编程方法: 1.面向对象:类--class 2.面向过程:过程--def 3.函数式编程:函数--def #python中函数#函数的定义#打印一个佛祖镇楼 -> 一个功能点的解释 defpri ...
- if python 判断函数返回值_Python函数的返回值和作用域
函数的返回值和作用域 1.返回值 def guess(x): if x > 3: return "> 3" else: return "<= 3&quo ...
- python 函数递归一次增加一次变量_python基础之函数、返回值,局部变量、全局变量,递归(继续补充不定长参数)...
1.python中函数定义:函数是逻辑结构化和过程化的一种编程方法.(完成某一种特定的功能) def test02(): #"" msg = 'hello WuDaLang' re ...
最新文章
- VMware虚拟机安装红帽系统无法上网解决办法(转)
- 博士申请 | 英国爱丁堡大学NLP组招收自然语言处理方向全奖博士生
- 【C++教程】02.环境配置
- DockerCompose-部署微服务集群
- 华为p4用鸿蒙系统吗_华为mate40是鸿蒙系统吗
- 计时器小程序——由浅入深实例讲解
- java 面试什么是类_Java 面试题代码类收集
- oracle单表存储记录,oracle从各个表获得数据保存到另一个表
- Kaldi(A1)语音识别原理
- 设计模式笔记之二(工厂模式)
- matlab 倒位序fft程序,[转载]MATLAB的一个FFT程序
- SPSS Modeler 数据导入操作
- BeanAir—无线传感器测试方案
- 产品原型设计实战(一):产品设计相关工作
- 24考研数学复习方法、全年规划
- Java数字图像处理基础-------Java Swing简单使用,图形绘画---画五角星
- 电脑页面怎么没有计算机,如何找回电脑里消失的IE浏览器图标?
- 再见,马云!再见,世界首富!
- 月结2 - 维护汇率[OB08]
- python处理ip模块,Ipy,ipaddr
热门文章
- Mysql,CASE WHEN 函数使用.
- 「管理数学基础」1.7 矩阵理论:方阵特征值估计、圆盘定理、谱与谱半径
- Cooperative Perception协同感知学习记录
- 乱序字典加密解密python基础知识综合应用
- 2021年中国智慧菜场行业研究报告 附下载
- python多重继承初始化顺序_Python 多重继承顺序
- 向日葵远程控制的使用方法
- 张柏芝王菲周迅 细数谢霆锋十段“姐弟恋”情史
- 推荐一个国外用Flex制作播放器的源码
- 济南高新区支撑“一次办好”的“区块链+政务服务”平台