函数调用传形参,何时才会创建临时变量?

sup945 2014-07-08 08:37:57
源代码:

#include <string>
#include <iostream>
using namespace std;
string version1(string s1, string s2);
int main()
{
string input="unix";
string input2="***";
string result;

result = version1(input, input2);
return 0;
}

string version1(string s1, string s2)
{
s1 = s2 + s1 + s2;
return s1;
}

main 函数中作出临时变量的代码
0x0000000000400a7e <+106>: callq 0x400850 <_ZNSsC1Ev@plt>
0x0000000000400a83 <+111>: lea -0x60(%rbp),%rdx
0x0000000000400a87 <+115>: lea -0x30(%rbp),%rax
0x0000000000400a8b <+119>: mov %rdx,%rsi
0x0000000000400a8e <+122>: mov %rax,%rdi

0x0000000000400a91 <+125>: callq 0x400880 <_ZNSsC1ERKSs@plt>
0x0000000000400a96 <+130>: lea -0x70(%rbp),%rdx
0x0000000000400a9a <+134>: lea -0x40(%rbp),%rax
0x0000000000400a9e <+138>: mov %rdx,%rsi
0x0000000000400aa1 <+141>: mov %rax,%rdi

0x0000000000400aa4 <+144>: callq 0x400880 <_ZNSsC1ERKSs@plt>
0x0000000000400aa9 <+149>: lea -0x20(%rbp),%rax
0x0000000000400aad <+153>: lea -0x30(%rbp),%rdx
0x0000000000400ab1 <+157>: lea -0x40(%rbp),%rcx
0x0000000000400ab5 <+161>: mov %rcx,%rsi
0x0000000000400ab8 <+164>: mov %rax,%rdi
0x0000000000400abb <+167>: callq 0x400bc3 <version1(std::string, std::string)>

红色字体为复制input的临时变量,蓝色字体为复制input2的临时变量

version1中收input和input1的代码:
Dump of assembler code for function version1(std::string, std::string):
0x0000000000400bc3 <+0>: push %rbp
0x0000000000400bc4 <+1>: mov %rsp,%rbp
0x0000000000400bc7 <+4>: push %rbx
0x0000000000400bc8 <+5>: sub $0x48,%rsp
0x0000000000400bcc <+9>: mov %rdi,-0x38(%rbp)
0x0000000000400bd0 <+13>: mov %rsi,-0x40(%rbp)
0x0000000000400bd4 <+17>: mov %rdx,-0x48(%rbp)
0x0000000000400bd8 <+21>: lea -0x30(%rbp),%rax
0x0000000000400bdc <+25>: mov -0x40(%rbp),%rdx // copy of input
0x0000000000400be0 <+29>: mov -0x48(%rbp),%rcx // copy of input2


调用version1 函数后的堆栈情况:

0x7fffffffe0c0: 0xffffe270 0x00007fff 0xffffe160 0x00007fff
0x7fffffffe0d0: 0xffffe150 0x00007fff 0xffffe170 0x00007fff
0x7fffffffe0e0: 0xffffe130 0x00007fff 0xffffe130 0x00007fff
0x7fffffffe0f0: 0xffffe160 0x00007fff 0x00000003 0x00000000
0x7fffffffe100: 0x00000010 0x00000000 0x00000000 0x00000000
0x7fffffffe110: 0xffffe190 0x00007fff 0x00400ac0 0x00000000
0x7fffffffe120: 0x00603028 0x00000000 0x00400cb8 0x00000000
0x7fffffffe130: 0x00603058 0x00000000 0x0000ffff 0x00000001
0x7fffffffe140: 0xf7dc74d8 0x00007fff 0x00400ccd 0x00000000
0x7fffffffe150: 0x00603028 0x00000000 0x00400d9d 0x00000000
0x7fffffffe160: 0x00603058 0x00000000 0x00400d40 0x00000000
0x7fffffffe170: 0x00000000 0x00000000 0x00400930 0x00000000
0x7fffffffe180: 0xffffe270 0x00007fff 0x00000000 0x00000000
0x7fffffffe190: 0x00000000 0x00000000 0xf752576d 0x00007fff
0x7fffffffe1a0: 0x00000000 0x00000000 0xffffe278 0x00007fff

0x7fffffffe110: version1 栈顶
0x7fffffffe190: main 栈顶
0x7fffffffe120: main input
0x7fffffffe130: main input2
0x7fffffffe150: copy of main input (临时变量1)
0x7fffffffe160: copy of main input2 (临时变量2)


很清楚的看到 字符串被拷贝了一份后传给了version , 而上述代码如果将string 换为int则完全不会拷贝临时变量传给version,c++传形参时何时创建临时变量,何时不创建,有何规则可依据吗?
...全文
343 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
sup945 2014-07-11
  • 打赏
  • 举报
回复
引用 11 楼 unituniverse2 的回复:
对象以值传递方式(相对于引用传递方式)做函数参数的时候主要是考虑到不能修改原对象的内容而做拷贝,和优不优化无关(或者说编译器即使做任何优化都不能改变这个行为)。若是换成像int这样的内置类型,主要还是传统的优化方式起作用。如果你做多次函数调用传参,因为现在调用时候用到的参数在后面还会用到,就可以看到实际上还是会做拷贝。而如果只是一行的话,拷贝一份后留着原值不用没有任何意义。。如果你觉得想让对象也做这种优化,会改变语义(因为这种优化可以看作是把左值当作右值传参给函数了,而在用户没有显式的指明移动语意的话是不允许的)。当前的类似copy elision优化甚至都允许在改变程序的行为的情况下依然做这类优化,但是这类优化的前提是不能改变语义(比如未经用户指明就把一个左值变成右值传递了。而copy elision发生在几次右值移动的时候减少右值的总数,并没有改变语义) 至于如果还想问返回值方面的优化,论坛里有看到讨论过很多次,可以找找看。。。
找到一篇“C++传参和返回值深度探索” 很有帮助,感谢
赵4老师 2014-07-09
  • 打赏
  • 举报
回复
引用 4 楼 sup945 的回复:
calling 赵4
计算机组成原理→DOS命令→汇编语言→C语言(不包括C++)、代码书写规范→数据结构、编译原理、操作系统→计算机网络、数据库原理、正则表达式→其它语言(包括C++)、架构…… 对学习编程者的忠告: 眼过千遍不如手过一遍! 书看千行不如手敲一行! 手敲千行不如单步一行! 单步源代码千行不如单步Debug版对应汇编一行! 单步Debug版对应汇编千行不如单步Release版对应汇编一行! VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口看每句C对应的汇编、单步执行并观察相应堆栈、内存和寄存器变化,这样过一遍不就啥都明白了吗。 对VC来说,所谓‘调试时’就是编译连接通过以后,按F10或F11键单步执行一步以后的时候,或者在某行按F9设了断点后按F5执行停在该断点处的时候。 (Turbo C或Borland C用Turbo Debugger调试,Linux或Unix下用GDB调试时,看每句C对应的汇编并单步执行观察相应内存和寄存器变化。)
unituniverse2 2014-07-09
  • 打赏
  • 举报
回复
引用 10 楼 sup945 的回复:
[quote=引用 8 楼 zhao4zhong1 的回复:] [quote=引用 4 楼 sup945 的回复:] calling 赵4
单步源代码千行不如单步Debug版对应汇编一行! [/quote] 四哥,俺是gdb调的,不管release版本还是Debug版本都有临时变量哦,以下几个debug参数搞上都带临时变量 Makefile: CC = g++ #DEBUG = -gstabs+ -Wall #DEBUG = -g DEBUG = FILES = string string.s stringref stringref.s all : ${FILES} string : string.cpp ${CC} string.cpp ${DEBUG} -o string [/quote] 对象以值传递方式(相对于引用传递方式)做函数参数的时候主要是考虑到不能修改原对象的内容而做拷贝,和优不优化无关(或者说编译器即使做任何优化都不能改变这个行为)。若是换成像int这样的内置类型,主要还是传统的优化方式起作用。如果你做多次函数调用传参,因为现在调用时候用到的参数在后面还会用到,就可以看到实际上还是会做拷贝。而如果只是一行的话,拷贝一份后留着原值不用没有任何意义。。如果你觉得想让对象也做这种优化,会改变语义(因为这种优化可以看作是把左值当作右值传参给函数了,而在用户没有显式的指明移动语意的话是不允许的)。当前的类似copy elision优化甚至都允许在改变程序的行为的情况下依然做这类优化,但是这类优化的前提是不能改变语义(比如未经用户指明就把一个左值变成右值传递了。而copy elision发生在几次右值移动的时候减少右值的总数,并没有改变语义) 至于如果还想问返回值方面的优化,论坛里有看到讨论过很多次,可以找找看。。。
sup945 2014-07-09
  • 打赏
  • 举报
回复
引用 8 楼 zhao4zhong1 的回复:
[quote=引用 4 楼 sup945 的回复:] calling 赵4
单步源代码千行不如单步Debug版对应汇编一行! [/quote] 四哥,俺是gdb调的,不管release版本还是Debug版本都有临时变量哦,以下几个debug参数搞上都带临时变量 Makefile: CC = g++ #DEBUG = -gstabs+ -Wall #DEBUG = -g DEBUG = FILES = string string.s stringref stringref.s all : ${FILES} string : string.cpp ${CC} string.cpp ${DEBUG} -o string
sup945 2014-07-09
  • 打赏
  • 举报
回复
单步Debug版对应汇编千行不如单步Release版对应汇编一行! 四哥,俺是gdb调的,不管release版本还是Debug版本都有临时变量哦,以下几个debug参数搞上都带临时变量 Makefile: CC = g++ #DEBUG = -gstabs+ -Wall #DEBUG = -g DEBUG = FILES = string string.s stringref stringref.s all : ${FILES} string : string.cpp ${CC} string.cpp ${DEBUG} -o string
taodm 2014-07-08
  • 打赏
  • 举报
回复
抱歉,有些优化是编译器一定要启用的,你根本无法禁止的。并且,正好这个临时变量问题就在“有些”里面。
引用 6 楼 sup945 的回复:
原帖编译未经过任何优化
sup945 2014-07-08
  • 打赏
  • 举报
回复
原帖编译未经过任何优化
taodm 2014-07-08
  • 打赏
  • 举报
回复
不要写依赖于编译器会不会做优化的代码才是正道。 不要浪费时间在其具体细节上。你又不可能穷举所有的编译器、所有的编译参数的。
sup945 2014-07-08
  • 打赏
  • 举报
回复
calling 赵4
sup945 2014-07-08
  • 打赏
  • 举报
回复
fno-elide-constructors The C++ standard allows an implementation to omit creating a temporary which is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases. 加了拷贝的还要多 0x7fffffffe100: 0x00603028 0x00000000 0xf753fbf8 0x00007fff 0x7fffffffe110: 0x00603028 0x00000000 0xffffe140 0x00007fff 0x7fffffffe120: 0x00603058 0x00000000 0x00400d4f 0x00000000 0x7fffffffe130: 0x00603058 0x00000000 0x0000ffff 0x00000001 0x7fffffffe140: 0xf7dc74d8 0x00007fff 0x00400d64 0x00000000 0x7fffffffe150: 0x00603028 0x00000000 0x00400e4d 0x00000000 0x7fffffffe160: 0x00603058 0x00000000 0x00400df0 0x00000000
mujiok2003 2014-07-08
  • 打赏
  • 举报
回复
g++选项-fno-elide-constructors可以关闭NRVO, RVO 在c++ 11中, 考虑move constructor, move assigment
mujiok2003 2014-07-08
  • 打赏
  • 举报
回复
考虑优化程度

64,648

社区成员

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

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