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)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值