[C/C++高手进] 函数返回值与返回局部对象的引用!

FengPrince 2013-09-02 09:29:56
主要有2个问题:

大家都知道一条规则:不能返回局部对象的引用。
而且编译器一般也会提示“reference to local variable returned"!

假设定义了类Test:

Test& f()
{
Test t;
t.i=1;
return t;
}

Test g()
{
Test t;
t.i=1;
return t;
}
int main()
{
Test t1=f(); //---(1)
Test t2=g(); //---(2)
}

比较语句(1)和语句(2):
语句(1)在调用f()函数后其栈空间被释放,但数据仍在栈中,而且不会因其它因素而导致数据损坏,因为当前栈帧为main函数的,栈顶指针比返回引用对象t的地址要高!
语句(2)很简单,但实际情况与想像的不一样。

第1个问题
我的理解是:如果返回一个局部对象的引用,程序员能够确保不会对该引用的对象进行修改,只用来读取,那么即使是这种做法不好,也不会导致运行出错。同时,既然是只读应该返回const引用,这样就可以避免去修改引用的局部对象(当然,绕开编译器强行修改该对象除外)!

语句2,我通过汇编发现实际情况有点不同寻常。我不知道,编译器是否都做了优化,我是在LINUX X64 GCC下编译的。汇编代码的结果让我很吃惊:实际调用为:g(&t2);而函数g()变成了如下形式

void g(Test* t)
{
t->i=1;
}

语句2比语句1效率更高!!
PS:从另一方面也可以验证:语句(1)会调用复制构造函数;而语句(2)不会!!

第2个问题
照样正常理解,语句2应该返回一个临时对象;如果返回临时对象,那么临时对象到底存放在栈的哪个位置(寄存器是不可能了,Test类大小大于8Bytes),g()函数栈都已经收回了,临时对象难道放在main函数栈中?

多谢!!
...全文
520 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
max_min_ 2013-09-03
  • 打赏
  • 举报
回复 1
数据在栈中可能没有立即擦除内存地址而已! 导致可以拿到有效数据的
赵4老师 2013-09-03
  • 打赏
  • 举报
回复
加static
FengPrince 2013-09-03
  • 打赏
  • 举报
回复
引用 13 楼 akirya 的回复:
[quote=引用 11 楼 FengPrince 的回复:] [quote=引用 9 楼 akirya 的回复:] 编译器可以优化掉一些无关紧要的拷贝构造 gcc直接优化掉了 典型的例子就是 string s = "aaaa";理论上是先执行一次带参数构造,再执行一次拷贝.但实际上就执行一次构造
这个我知道,虽然不需要调复制构造函数,但复制构造函数的语义要求必须得满足,比如不能为private! 我想知道的是,返回值类型时VS会不会把返回的“临时对象”与被调用函数中局部变量当成同一个对象。 就像你举的例了中:test f(){ test x;return x;} t=f();(赋值语句而非初始化) VS会不会像GCC那样,在f()栈中创建x,然后返回时,再在main栈中创建与x相同的临时对象,最后将临时对象复制到main的局部变量t中! 麻烦了!!![/quote] 如果不开优化,那么就是2个对象.开优化就会像gcc一样就是1个对象.[/quote] 十分感谢!! PS:已经结帖了,没有分,望谅解!
  • 打赏
  • 举报
回复
引用 11 楼 FengPrince 的回复:
[quote=引用 9 楼 akirya 的回复:] 编译器可以优化掉一些无关紧要的拷贝构造 gcc直接优化掉了 典型的例子就是 string s = "aaaa";理论上是先执行一次带参数构造,再执行一次拷贝.但实际上就执行一次构造
这个我知道,虽然不需要调复制构造函数,但复制构造函数的语义要求必须得满足,比如不能为private! 我想知道的是,返回值类型时VS会不会把返回的“临时对象”与被调用函数中局部变量当成同一个对象。 就像你举的例了中:test f(){ test x;return x;} t=f();(赋值语句而非初始化) VS会不会像GCC那样,在f()栈中创建x,然后返回时,再在main栈中创建与x相同的临时对象,最后将临时对象复制到main的局部变量t中! 麻烦了!!![/quote] 如果不开优化,那么就是2个对象.开优化就会像gcc一样就是1个对象.
FengPrince 2013-09-03
  • 打赏
  • 举报
回复
引用 11 楼 FengPrince 的回复:
[quote=引用 9 楼 akirya 的回复:] 编译器可以优化掉一些无关紧要的拷贝构造 gcc直接优化掉了 典型的例子就是 string s = "aaaa";理论上是先执行一次带参数构造,再执行一次拷贝.但实际上就执行一次构造
这个我知道,虽然不需要调复制构造函数,但复制构造函数的语义要求必须得满足,比如不能为private! 我想知道的是,返回值类型时VS会不会把返回的“临时对象”与被调用函数中局部变量当成同一个对象。 就像你举的例了中:test f(){ test x;return x;} t=f();(赋值语句而非初始化) VS会不会像GCC那样,在f()栈中创建x,然后返回时,再在main栈中创建与x相同的临时对象,最后将临时对象复制到main的局部变量t中! 麻烦了!!![/quote] VS会不会不像GCC那样
FengPrince 2013-09-03
  • 打赏
  • 举报
回复
引用 9 楼 akirya 的回复:
编译器可以优化掉一些无关紧要的拷贝构造 gcc直接优化掉了 典型的例子就是 string s = "aaaa";理论上是先执行一次带参数构造,再执行一次拷贝.但实际上就执行一次构造
这个我知道,虽然不需要调复制构造函数,但复制构造函数的语义要求必须得满足,比如不能为private! 我想知道的是,返回值类型时VS会不会把返回的“临时对象”与被调用函数中局部变量当成同一个对象。 就像你举的例了中:test f(){ test x;return x;} t=f();(赋值语句而非初始化) VS会不会像GCC那样,在f()栈中创建x,然后返回时,再在main栈中创建与x相同的临时对象,最后将临时对象复制到main的局部变量t中! 麻烦了!!!
FengPrince 2013-09-03
  • 打赏
  • 举报
回复
引用 6 楼 lin5161678 的回复:
[quote=引用 5 楼 FengPrince 的回复:] [quote=引用 1 楼 taodm 的回复:] 语句1既然是错误的,C++标准称之为“未定义行为”,发生啥结果都是正常的。 不要浪费时间在讨论错误的东西有啥“正确”的结果。
好吧,我知道是“未定义行为”。 我其实想问的是在自己完全可控的条件下,能否保证程序的正确性! 这就像char不能保证移植性,因为不同编译器可能实现的有无符号数不一样;但我只要保证使用char的值始终在可ANSI打印字符范围内,而且不用参与算术运算,照样可以跨平台!但很明显,这种做法风险比较大! 其实,有些“未定义行为”是完全可控的,但要自己保证,因为稍有不慎就可能出现纰漏。 我试过了,返回局部对象的引用的情况并非全部可控,因为会调用复制构造函数,进而破坏先前栈的数据,比如保存rdi和rsi寄存器。[/quote]w 你前面的对char的可控 没触犯未定义行为 用这个char 说明不了存在可控的未定义行为 另外不管实现为 有符号 还是无符号 根据你做出的保证 可以保证使用char没有风险 [/quote] 不知道是我没描述清楚还是我理解有误!! 我的意思是控制未定义行为的发生!

char c;
.
.
.
if(c>0X38)
   ...;
是不是可能导致未定义行为?或者准确来讲是Implementation Defined行为! 但如果我能够确保c的范围来保证此语句永远不会发生未定义行为。 同理,返回局部对象的引用,只有当局部对象所在的空间被污染了,才可能导致出错;没污染就不会出错。会不会被污染这一行为是未定义的! 我要做的就是确保该数据不被污染(破坏)! 或许我压根就做不到?? 这才是我想问的,就目前而言,没有人给出我不可控的证据! PS:准备结帖了,这个东西找工作意义不大!!
  • 打赏
  • 举报
回复
编译器可以优化掉一些无关紧要的拷贝构造 gcc直接优化掉了 典型的例子就是 string s = "aaaa";理论上是先执行一次带参数构造,再执行一次拷贝.但实际上就执行一次构造
  • 打赏
  • 举报
回复
引用 7 楼 FengPrince 的回复:
[quote=引用 3 楼 worldy 的回复:] 第一个函数,是返回函数栈中的数据,返回瞬间可以该数据仍然有效,但不保证 第二个函数,编译器会将函数栈中的返回值数据拷贝到返回栈中
第1个问题在于编译器可能会调用自定义的复制构造函数和赋值重载函数导致损坏先前栈中数据;当然,也可能有其它未知的原因(我这不费话吗?) 第2个问题确实是在返回栈中即在main栈中,我通过赋值操作然后汇编发现,在返回栈中的返回的临时变量其实也就是函数g()中的局部变量t。也就是说,g()中的局部变量也放在了main()栈中而非g()栈中,但g()中保存了指向其局部变量的指针! 事实上,如果返回被调用函数的局部变量的值类型,根本就没有所谓的”临时对象”,这个临时对象会被编译器优化成了被调用函数的局部对象!!这一点,我发现很多书都说错了,包括许多国外的书!当然,或许理论上是对的,我目前只有GCC,没试过VS编译器! PS:关于返回值类型会返回”临时对象”这个问题,我很早就持怀疑态度,因为我觉得编译器不一定非要进行多一步的复制操作,尤其是返回的是较大的类对象,这样效率比较低!兄弟如果有VS可以看看VS的处理方式,thx![/quote]
#include<stdio.h>

struct test
{
	~test()
	{
		puts( "~test" );
	}
	int val[10];
};

test f(){ test x;return x;}

int main()
{
	test x = f();
    return 0;
}
VS系列,都是输出2次~test
FengPrince 2013-09-03
  • 打赏
  • 举报
回复
引用 3 楼 worldy 的回复:
第一个函数,是返回函数栈中的数据,返回瞬间可以该数据仍然有效,但不保证 第二个函数,编译器会将函数栈中的返回值数据拷贝到返回栈中
第1个问题在于编译器可能会调用自定义的复制构造函数和赋值重载函数导致损坏先前栈中数据;当然,也可能有其它未知的原因(我这不费话吗?) 第2个问题确实是在返回栈中即在main栈中,我通过赋值操作然后汇编发现,在返回栈中的返回的临时变量其实也就是函数g()中的局部变量t。也就是说,g()中的局部变量也放在了main()栈中而非g()栈中,但g()中保存了指向其局部变量的指针! 事实上,如果返回被调用函数的局部变量的值类型,根本就没有所谓的”临时对象”,这个临时对象会被编译器优化成了被调用函数的局部对象!!这一点,我发现很多书都说错了,包括许多国外的书!当然,或许理论上是对的,我目前只有GCC,没试过VS编译器! PS:关于返回值类型会返回”临时对象”这个问题,我很早就持怀疑态度,因为我觉得编译器不一定非要进行多一步的复制操作,尤其是返回的是较大的类对象,这样效率比较低!兄弟如果有VS可以看看VS的处理方式,thx!
lin5161678 2013-09-03
  • 打赏
  • 举报
回复
引用 5 楼 FengPrince 的回复:
[quote=引用 1 楼 taodm 的回复:] 语句1既然是错误的,C++标准称之为“未定义行为”,发生啥结果都是正常的。 不要浪费时间在讨论错误的东西有啥“正确”的结果。
好吧,我知道是“未定义行为”。 我其实想问的是在自己完全可控的条件下,能否保证程序的正确性! 这就像char不能保证移植性,因为不同编译器可能实现的有无符号数不一样;但我只要保证使用char的值始终在可ANSI打印字符范围内,而且不用参与算术运算,照样可以跨平台!但很明显,这种做法风险比较大! 其实,有些“未定义行为”是完全可控的,但要自己保证,因为稍有不慎就可能出现纰漏。 我试过了,返回局部对象的引用的情况并非全部可控,因为会调用复制构造函数,进而破坏先前栈的数据,比如保存rdi和rsi寄存器。[/quote] 你前面的对char的可控 没触犯未定义行为 用这个char 说明不了存在可控的未定义行为 另外不管实现为 有符号 还是无符号 根据你做出的保证 可以保证使用char没有风险
FengPrince 2013-09-03
  • 打赏
  • 举报
回复
引用 1 楼 taodm 的回复:
语句1既然是错误的,C++标准称之为“未定义行为”,发生啥结果都是正常的。 不要浪费时间在讨论错误的东西有啥“正确”的结果。
好吧,我知道是“未定义行为”。 我其实想问的是在自己完全可控的条件下,能否保证程序的正确性! 这就像char不能保证移植性,因为不同编译器可能实现的有无符号数不一样;但我只要保证使用char的值始终在可ANSI打印字符范围内,而且不用参与算术运算,照样可以跨平台!但很明显,这种做法风险比较大! 其实,有些“未定义行为”是完全可控的,但要自己保证,因为稍有不慎就可能出现纰漏。 我试过了,返回局部对象的引用的情况并非全部可控,因为会调用复制构造函数,进而破坏先前栈的数据,比如保存rdi和rsi寄存器。
worldy 2013-09-02
  • 打赏
  • 举报
回复
第一个函数,是返回函数栈中的数据,返回瞬间可以该数据仍然有效,但不保证 第二个函数,编译器会将函数栈中的返回值数据拷贝到返回栈中
图灵狗 2013-09-02
  • 打赏
  • 举报
回复
++
引用 1 楼 taodm 的回复:
语句1既然是错误的,C++标准称之为“未定义行为”,发生啥结果都是正常的。 不要浪费时间在讨论错误的东西有啥“正确”的结果。
taodm 2013-09-02
  • 打赏
  • 举报
回复
语句1既然是错误的,C++标准称之为“未定义行为”,发生啥结果都是正常的。 不要浪费时间在讨论错误的东西有啥“正确”的结果。

65,212

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧