今天在review
以前的代码的时候,遇到了一个比较奇怪的现象,函数的有返回值,但只在if
后面有return
,else
后面忘写了。但这个版本的代码已经调试验证通过了,没有问题的,这就很怪异。
下面这道题Print
的内容是什么?
# include
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
语句,这会出现什么?
# include
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
么?也就是本文开始提出的那个问题。当然,答案也已经被证明出来了,并不需要。
在正式解释之前,再看一个例子:
# include
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
的同一变量地址的内容(栈被释放了)!
当然,这些都是通过区分代码得到的浅显的认知,但这究竟是什么原因呢?
一般来说,为了从一个函数得到运行结果,常规的途径有两个:通过返回值和通过传入函数的引用或指针。
当通过返回值的时候,如果是类的对象或指针的时候,需要注意拷贝构造函数!
拷贝构造函数通常有三种使用场景:
也就是说,当函数返回时,需要将栈上的对象通过拷贝构造函数,复制到调用该函数的返回值中去。即,把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)