【C++】C++函数需要有返回值,但非全分支return(RVO)
今天在review以前的代碼的時(shí)候,遇到了一個(gè)比較奇怪的現(xiàn)象,函數(shù)的有返回值,但只在if后面有return,else后面忘寫了。但這個(gè)版本的代碼已經(jīng)調(diào)試驗(yàn)證通過了,沒有問題的,這就很怪異。
考驗(yàn)一道題
下面這道題Print的內(nèi)容是什么?
# 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; }編譯并運(yùn)行:
g++ test.cpp -o test ./test可能你也能猜到最終的結(jié)果:
x : -1 y : -1也可能會(huì)疑問,在fun函數(shù)的else語句中并沒有提供返回值啊?為什么還能有輸出么?或者,會(huì)問函數(shù)的return語句不全,不是應(yīng)該報(bào)錯(cuò)么?
函數(shù)的返回值
沒有return語句
如果一個(gè)函數(shù)需要有返回值,但是沒有return語句,這會(huì)出現(xiàn)什么?
# 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; }編譯并運(yùn)行:
g++ test.cpp -o test ./test編譯沒有任何問題,但是在運(yùn)行的時(shí)候出現(xiàn)了錯(cuò)誤:
x : 2 y : H��H9�u�H�[]A\A]A^A_Df.���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] 已放棄 (核心已轉(zhuǎn)儲(chǔ))也就是說,如果一個(gè)函數(shù)聲明或定義了有返回值,那么就必須要有return語句!
那么,如果出現(xiàn)了分支語句,必須保證所有的出口都必須有return么?也就是本文開始提出的那個(gè)問題。當(dāng)然,答案也已經(jīng)被證明出來了,并不需要。
非全出口return語句
在正式解釋之前,再看一個(gè)例子:
# 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; }編譯并運(yùn)行:
g++ test.cpp -o test ./test編譯沒有任何問題,但是在運(yùn)行的時(shí)候出現(xiàn)了錯(cuò)誤:
x : 2 y : H��H9�u�H�[]A\A]A^A_Df.���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] 已放棄 (核心已轉(zhuǎn)儲(chǔ))和第二例沒有return的語句一樣,也出現(xiàn)了類似的錯(cuò)誤。但這段程序是有return語句的啊!
細(xì)細(xì)分析代碼的區(qū)別,其實(shí)可以發(fā)現(xiàn),分支中沒有return分支的函數(shù)出口,好像有一個(gè)默認(rèn)的返回值,就是有return語句分支的返回值變量的所在地址!
第一例中,else語句盡管沒有return語句,但是返回了一個(gè)與if語句中return的同一個(gè)變量地址的內(nèi)容;而第三例中,else語句中并沒有if語句中return的同一變量地址的內(nèi)容(棧被釋放了)!
當(dāng)然,這些都是通過區(qū)分代碼得到的淺顯的認(rèn)知,但這究竟是什么原因呢?
編譯器的鍋(RVO)
一般來說,為了從一個(gè)函數(shù)得到運(yùn)行結(jié)果,常規(guī)的途徑有兩個(gè):通過返回值和通過傳入函數(shù)的引用或指針。
當(dāng)通過返回值的時(shí)候,如果是類的對象或指針的時(shí)候,需要注意拷貝構(gòu)造函數(shù)!
拷貝構(gòu)造函數(shù)通常有三種使用場景:
也就是說,當(dāng)函數(shù)返回時(shí),需要將棧上的對象通過拷貝構(gòu)造函數(shù),復(fù)制到調(diào)用該函數(shù)的返回值中去。即,把return t的t復(fù)制到Test t = fun(false)的t中去。
而RVO(C++的返回值優(yōu)化)是指:C++標(biāo)準(zhǔn)允許一種(編譯器)實(shí)現(xiàn)省略創(chuàng)建一個(gè)只是為了初始化另一個(gè)同類型對象的臨時(shí)對象。基本手段是直接將返回的對象構(gòu)造在調(diào)用者棧幀上,這樣調(diào)用者就可以直接訪問這個(gè)對象而不必復(fù)制。指定這個(gè)參數(shù)(-fno-elide-constructors)將關(guān)閉這種優(yōu)化,強(qiáng)制G++在所有情況下調(diào)用拷貝構(gòu)造函數(shù)。
現(xiàn)在就清楚了,當(dāng)函數(shù)有一個(gè)return后,就會(huì)將該return的對象直接構(gòu)造在調(diào)用者棧上,就不需要走return的拷貝構(gòu)造函數(shù)。
當(dāng)然可以試驗(yàn)一下,利用-fno-elide-constructors關(guān)閉優(yōu)化:
編譯并運(yùn)行:
g++ -fno-elide-constructors test.cpp -o test ./test編譯沒有任何問題,但是在運(yùn)行的時(shí)候出現(xiàn)了錯(cuò)誤:
*** 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] 已放棄 (核心已轉(zhuǎn)儲(chǔ))也就是說,確實(shí)是因?yàn)镽VO(C++的返回值優(yōu)化)的原因。
當(dāng)然,保證每個(gè)分支出口都有return才是最重要的!
相關(guān)閱讀
【C++踩坑】說說g++的-fno-elide-constructors參數(shù)
C++的返回值優(yōu)化(RVO,Return Value Optimization)
總結(jié)
以上是生活随笔為你收集整理的【C++】C++函数需要有返回值,但非全分支return(RVO)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: flutter 真机无法调试 sdk报错
- 下一篇: 盛大发布nbsp;Bambooknbsp