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

  • 相关阅读:
    IntelliJ IDEA 14.03 java 中文文本处理中的编码格式设置
    应聘感悟
    STL string分析
    CUDA SDK VolumeRender 分析 (1)
    BSP
    CUDA SDK VolumeRender 分析 (3)
    CUDA SDK VolumeRender 分析 (2)
    Windows软件发布时遇到的一些问题
    Ten Commandments of Egoless Programming (转载)
    复习下光照知识
  • 原文地址:https://www.cnblogs.com/openix/p/3178259.html
Copyright © 2020-2023  润新知