当前位置: 代码迷 >> 综合 >> C++ 之函数返回局部变量
  详细解决方案

C++ 之函数返回局部变量

热度:100   发布时间:2023-09-18 13:32:17.0

1. 返回字符串字面量的指针,即存放在常量存储区的数据,不会因为函数调用栈被释放而消失,所以操作可行。

char* const_str()
{char* p = "Hello World!"; // 指向常量区字符串的指针return p;
}

另外,其实这里写的不规范,C++11 标准要求在字面量指针增加 const,以防止猿们随意改变其内容导致段错误。

 

2. 返回局部变量的 reference 引用

首先,不管是基本数据类型还是对象,返回局部引用都是错误的。返回的别名引用了函数内局部变量,它的数据区是放在栈空间的,在函数调用结束后,栈空间被释放之后就不存在了,其数据可能会被覆盖掉。

2.1 普通变量的引用 

通常编译器会在 return i; 处放出警告,如 xcode Reference to stack memory associated with local variable 'i' returned

打印出的地址是一样的,说明返回的引用并没有真正的重新拷贝复制一份到新的栈空间,即 main 函数栈中,而是直接将它给了引用变量 p。

如果不是引用,则会拷贝一份新的内存,两者地址是不同的。

int& refer()
{int i = 99;cout << &i << endl; // 0x7ffeefbff4acreturn i;
}void main() 
{int& p = refer();cout << &p << endl; // 0x7ffeefbff4accout << p << endl; // 32767(???)
}
int refer()
{int i = 99;cout << &i << endl; // 0x7ffeefbff4acreturn i;
}void main() 
{   int p = refer();cout << &p << endl; // 0x7ffeefbff4e4cout << p << endl; // 99
}

2.2 局部对象的引用

自定义了一个 Tools,data 初始化为 "Hello, World"

问:什么情况,A 跟 B 的结果居然不一样?

答:原因在于编译器在碰到 “=” 时在背后调用了默认的拷贝构造函数,即 Tools:Tools(const Tools& rhs);

data 指针也默认来自 rhs,即等号右手边的对象。这时如果把析构函数的注释去掉,运行时就会报错 effectiveC++(6837,0x1000f45c0) malloc: *** error for object 0x1005401d0: pointer being freed was not allocated

 问:是不是真的调用了拷贝构造呢?

答:自己增加该函数 cout 一下便可以清楚看到。

注意??,从这里可以看出:如果对象中包含非普通数据成员变量(int, double...),最好明确给出拷贝构造函数的拷贝方式,保证可靠。

class Tools
{
public:const char* data;Tools(){data = new char[20]{'H','e', 'l', 'l', 'o', ',', 'W', 'o', 'r', 'l', 'd'};}/** ~Tools(){delete [] data; // release memery}**/
};Tools& fun()
{Tools tool;cout << &tool << endl; // 0x7ffeefbff4b8return tool;
}
/**
A. 没有赋值操作
**/
void main()
{cout << &fun() << endl; // 0x7ffeefbff4b8
}/**
B. 有赋值操作
*/
void main()
{Tools tool = fun();cout << &tool << endl; // 0x7ffeefbff4e0
}

3. 局部对象

这个是本笔记?要关注的,返回局部对象并非通常所说的拷贝一个临时变量作为返回,即调用拷贝构造函数(不论有无赋值操作)。现代编译器都进行了优化,即 return value optimization (RVO)。编译器遇到这样的函数其实做了不少的更改,以保证最后的运行结果是你预期的,同时避免了拷贝内存这样费时的工作。

首先来看看下面的代码,想想会产生什么样的输出:

class C
{
public:C() = default;C(const C&){cout << "copy C" << endl;}
};C F()
{return C();
}
void main()
{C c = F();
}

是的,什么都不会输出。问题又来了,如果按照刚刚的解释,栈空间被释放,F 返回的局部对象 C 应该被重新拷贝一份才对,这时会调用拷贝构造函数。如果用不同的编译器,你可能会看到三种不同的情况,这里是一种,下面是另外两种:

copy C
copy C
copy C

原因都在于编译器生成了不同的"优化"代码。

我这里简单写一下大致第三种的优化情况?

C* F(C* _hiddenAddress)
{C local; // local Object in F*_hiddenAddress = local;// copy once after assignmentreturn _hiddenAddress;
}void main()
{C _c; // 之所以不用指针是因为最后还要 delete _c;C c = *F(&_c); // copy twice after return
}

这个优化真的是太棒了!成功拷贝了两次,超乎想象?

所以后来怎么改的呢,是的,用过库函数那些字符串处理函数的应该清楚,都是外部先声明变量开辟空间,再将地址传入函数体内进行赋值,这样就避免了所有的拷贝操作。于是,上面的调用就被改成这样,对的,F 什么都没干。

void F(C* p)
{
}void main()
{C c;F(&c);
}

为了证明这一点,我们可以在 F 中打印地址进行对比,结果是一致的。当然,为了保证程序的正常执行顺序,优化的过程会比这复杂。

C F()
{C c;cout << "c1: " << &c << endl; // c1: 0x7ffeefbff4e0return c;
}void main()
{C c = F();cout << "c2: " << &c << endl; // c2: 0x7ffeefbff4e0
}

注意??,还记得刚刚那个析构函数重复释放 data 的错误吗,为了说明返回的 c 并不会因为指向相同的内存区域,被函数返回时释放第二次出错,现在再重新验证。

class C
{
public:const char* data;C(){data = new char[20]{'H','e', 'l', 'l', 'o', ',', 'W', 'o', 'r', 'l', 'd'};}C(const C&){cout << "copy C" << endl;}~C(){cout << "delete C" << endl;delete [] data;}
};C F()
{C c;return c; // 局部对象 c 并不会被释放
}void main()
{C c = F(); // ~C 也只会被执行一次// ~C()
}

4. 返回的是局部对象的指针,但赋值时是对象 Object 类型

这时候就像[3]所讲,要重新拷贝新的内存到等号左边赋值。

同样是上边的例子

class C
{
public:C() = default;C(const C&){cout << "copy C" << endl;}~C(){cout << "delete C" << endl;}
};C* F()
{C c;return &c; // delete c
}
void main()
{C c = *F(); // copy c// delete c
}
delete C
copy C
delete C

5. 返回的是局部对象指针,直接赋值指针,不允许?

原因很清楚,局部对象已经被释放了。

void main()
{C *c = F();cout << "c 还要用呢" << endl;
}
delete C
c 还要用呢