如果返回结构体类型变量(named return value optimisation,NRVO)

貌似这是一个非常愚蠢的问题,因为对于具有良好素质的程序员而言,在C中函数返回类型为结构体类型是不是有点不合格,干嘛不用指针做传入传出呢?

测试环境:Linux IOS 3.2.0-45-generic-pae #70-Ubuntu SMP Wed May 29 20:31:05 UTC 2013 i686 i686 i386 GNU/Linux

                 gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
                 Copyright (C) 2011 Free Software Foundation, Inc.
                 This is free software; see the source for copying conditions.  There is NO
                  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 

此处谈下如果在C函数返回类型为大的结构体类型:

C++中严格区分初始化和赋值,但是C中没有区分初始化和赋值。

 1 //该程序引述自:http://bbs.chinaunix.net/forum.php?mod=viewthread&action=printable&tid=1651248
 2 //此链接中也有关于此文的讨论
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 
 8 const char *str = "Hello World\n";
 9 typedef struct
10 {
11     int m_Member1;
12     int m_Member2;
13     char m_String[20];
14 }FUNCTION_STRUCT;
15 
16 FUNCTION_STRUCT ReturnStruct(void)
17 {
18     FUNCTION_STRUCT internalData;
19     internalData.m_Member1 = 1;
20     internalData.m_Member2 = 2;
21     strcpy(&(internalData.m_String[0]), str);
22 
23     return internalData;
24 }
25 
26 int main(void)
27 {
28     FUNCTION_STRUCT externalData;
29     externalData = ReturnStruct();
30 
31     int a = externalData.m_Member1;
32     int b = externalData.m_Member2;
33     int c = a + b;
34 #if 0                                                                                                              
35     printf("%d, %d, %s",
36            externalData.m_Member1,
37            externalData.m_Member2,
38            &externalData.m_String[0]);
39 #endif
40 
41     return 0;
42 }


看下29行,实际上无论对于C或者C++(以文中开始处的测试环境为依据),ReturnStruct()都是有一个隐含的参数,其数据类型就是FUNCTION_STRUCT*,其存储空间在caller的栈中。

对C:

       ReturnStruct中internalData在ReturnStruct函数的栈空间,当其执行到“return internalData"之前,会把internalData中的数据一个一个的拷贝到隐含参数所指向的空间中。那么开始传入的隐含参数与externalData的地址空间是否相同呢?答:当为”#if 0“时隐含参数与externalData的地址空间相同,故此时只有”一次“生成internalData+”一次“拷贝到externalData(编译器完成),当为“#if 1"时,隐含参数与externalData的地址空间不同,因此当从ReturnStruct中返回时,在caller中由编译器插入一些操作,将隐含参数指向的空间拷贝到externalData的地址空间,故此时只有”一次“生成internalData+"两次”拷贝(编译器完成);如果我们用指针作为传入传出参数,对C而言,效率可以大大提高,因为只需”一次“赋值到externalData。对于C而言,如果函数返回大的结构提类型,将callee中的栈帧的相应值拷贝到caller中的临时参数的地址空间是不可避免的,可能的区别就在于:在caller中是否要将临时参数所所指的地址空间的数据拷贝到目标空间(临时参数所指的地址空间与目标空间相同,则不用拷贝)。

      所以对于C函数而言,如果写出的函数返回大的结构体数据类型,真的可以说不是一名合格的程序员(感觉返回小的结构体数据类型也不好啊,当然对于mips而言,返回8字节空间大小的结构体数据类型而言,直接用寄存器就可以了),即使出于可读性而言也不应该如此设计。

 

      问题在于C++中,要是有程序员如此设计,我还真的不知该如何评价,因为当我们在函数中返回一个类类型对象时,有时既可以与显式的使用指针设计的函数效率相同,而且可读性也大大加强。感觉这与C++中严格区分初始化和赋值有关(我不确定)。

 1 When certain criteria are met, an implementation is allowed to omit the copy construction of a class object,
 2 even if the copy constructor and/or destructor for the object have side effects. In such cases, the implementation
 3 treats the source and target of the omitted copy operation as simply two different ways of referring to
 4 the same object, and the destruction of that object occurs at the later of the times when the two objects
 5 would have been destroyed without the optimization.111) This elision of copy operations is permitted in the
 6 following circumstances (which may be combined to eliminate multiple copies):
 7in a return statement in a function with a class return type, when the expression is the name of a
 8 non-volatile automatic object with the same cv-unqualified type as the function return type, the copy
 9 operation can be omitted by constructing the automatic object directly into the function’s return value
10 — when a temporary class object that has not been bound to a reference (12.2) would be copied to a class
11 object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary
12 object directly into the target of the omitted copy
13 
14 [Example:
15 class Thing {
16 public:
17 Thing();
18 ˜Thing();
19 Thing(const Thing&);
20 };
21 Thing f() {
22 Thing t;
23 return t;
24 }
25 Thing t2 = f();
26 Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing:
27 the copying of the local automatic object t into the temporary object for the return value of function f()
28 and the copying of that temporary object into object t2. Effectively, the construction of the local object t
29 can be viewed as directly initializing the global object t2, and that object’s destruction will occur at program
30 exit. —end example]

上面时引述链接中所涉及的一段说明,我不是太明白,附下如下一段代码和运行结果,可以说明上文中的内容

 1 #include <iostream>                                                                                                
 2 
 3 class BASE
 4 {
 5     private:
 6         int val;
 7     public:
 8         BASE(void):val(5)
 9     {
10         std::cout << "BASE constructor" << std::endl;
11         std::cout << "own address: " << this << std::endl;
12     }
13 
14         BASE(const BASE& base)
15         {       
16             std::cout << "BASE copy constructor" << std::endl;
17             std::cout << "parameter address: " << &base << std::endl;
18             std::cout << "own address: " << this << std::endl;
19             val = base.val;
20         }       
21 
22         BASE& operator= (const BASE& base)
23         {       
24             std::cout << "BASE assignment" << std::endl;
25             std::cout << "parameter address: " << &base << std::endl;
26             std::cout << "own address: " << this << std::endl;
27             val = base.val;
28             return *this;
29         }       
30 
31         ~BASE(void)
32         {       
33             std::cout << "BASE deconstructor" << std::endl;
34             std::cout << "own address: " << this << std::endl;
35         }       
36 };
37 
38 BASE getBASE(void)
39 {
40     BASE base;
41     std::cout << "in getBASE base address:  " << &base << std::endl;
42     return base;
43 }
44 
45 int main(void)
46 {
47     BASE base_one = getBASE();
48     std::cout << "***********" << std::endl;
49     BASE base_two;
50     std::cout << "***********" << std::endl;
51     base_two = getBASE();
52     return 0;
53 }                  
 1 BASE constructor
 2 own address: 0xbfecb0d4
 3 in getBASE base address:    0xbfecb0d4
 4 ***********
 5 BASE constructor
 6 own address: 0xbfecb0d8
 7 ***********
 8 BASE constructor
 9 own address: 0xbfecb0dc
10 in getBASE base address:    0xbfecb0dc
11 BASE assignment
12 parameter address: 0xbfecb0dc
13 own address: 0xbfecb0d8
14 BASE deconstructor
15 own address: 0xbfecb0dc
16 BASE deconstructor
17 own address: 0xbfecb0d8
18 BASE deconstructor
19 own address: 0xbfecb0d4

首先,该程序范例不好,我没写/找出好点的范例。

关键处在于:

“BASE base_one = getBASE();"

"1 BASE constructor

 2 own address: 0xbfecb0d4

 3 in getBASE base address: 0xbfecb0d4

 4 ***********"

仅调用了一次构造,而且程序的可阅读性加强了。

注意g++的命令行参数 -fno-elide-constructors

posted on 2013-07-08 16:29  阿加  阅读(1698)  评论(0编辑  收藏  举报

导航