如果返回结构体类型变量(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

你可能感兴趣的:(return)