zoukankan      html  css  js  c++  java
  • 如果返回结构体类型变量(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
    ";
     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

  • 相关阅读:
    ZOJ 1649: Rescue(BFS)
    UVA
    hdu2458:Kindergarten (最大独立集)
    hdu3829:Cat VS Dog (最大独立集)
    Java 泛型
    request.getParameter() 和request.getAttribute() 区别
    Solr版本安装部署指南
    java.sql.SQLException: Incorrect string value: 'xE6x88x91xE7x9Ax84...' for column 'groupName'
    Incorrect string value: 'xF0x9Fx98x84xF0x9F
    java里面byte数组和String字符串怎么转换
  • 原文地址:https://www.cnblogs.com/openix/p/3178259.html
Copyright © 2011-2022 走看看