C++中stack的pop()函数返回值解析_第1页
C++中stack的pop()函数返回值解析_第2页
C++中stack的pop()函数返回值解析_第3页
C++中stack的pop()函数返回值解析_第4页
C++中stack的pop()函数返回值解析_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第C++中stack的pop()函数返回值解析目录stack的pop()函数返回值全部demo分析C++的返回值优化从函数返回值RVO

stack的pop()函数返回值

inttemp=s.pop();

couttempendl;

运行代码会提示错误:errorC2440:初始化:无法从void转换为int

全部demo

#includeiostream

#includestack

usingnamespacestd;

intmain()

stackint

if(s.empty())

cout"empty"endl;//empty

s.push(1);

s.push(6);

s.push(66);

couts.size()endl;//3

inttemp=s.pop();

couttempendl;//66

couts.size()endl;//2

couts.top()endl;//6

couts.size()endl;//2

system("pause");

return0;

}

分析

C++中stack,其中有两个方法:

pop(),返回void,top(),返回栈顶的引用。

所以想要提取栈顶元素,直接用s.top()

C++的返回值优化

大家都知道过早的优化是万恶之源这句话,然而我相信其中的大多数人都不知道自己是不是在做过早的优化。我也无法准确的定义什么叫做过早的优化,但我相信这过早的优化要么是得不偿失的,要么干脆是有害无利的。今天我就想举个我认为是过早的优化的例子。

从函数返回值

为了从一个函数得到运行结果,常规的途径有两个:通过返回值和通过传入函数的引用或指针(当然还可以通过全局变量或成员变量,但我觉得这算不上是什么好主意)。

通过传给函数一个引用或指针来承载返回值在很多情况下是无可厚非的,毕竟有时函数需要将多个值返回给用户。除了这种情况之外,我觉得应当尽量做到参数作为函数输入,返回值作为函数输出(这不是很自然的事情吗?)。然而,我们总能看到一些突破常规的做法:

首先定义Message类:

structMessage

inta;

intb;

intc;

intd;

inte;

intf;

};

为了从某个地方(比如一个队列)得到一个特定Message对象,有些人喜欢写一个这样的getMessage:

voidgetMessage(Messagemsg);//形式1

虽然只有一个返回值,但仍然是通过传入函数的引用返回给调用者的。

为什么要这样呢?嗯,为了提高性能。你知道,要是这样定义函数,返回Message对象时必须要构造一个临时对象,这对性能有影响。

MessagegetMessage();//形式2

我们先不讨论这带来了多少性能提升,先看看形式1相对形式2带来了哪些弊端。我认为有两点:

1.可读性变差

略(我希望你能和我一样认为这是显而易见的)。

2.将对象的初始化划分成了两个步骤

调用形式1时,你必然要这样:

Messagemsg;

//S1

getMessage(msg);//S2

这给维护者带来了犯错的机会:一些需要在S2语句后面对msg进行的操作有可能会被错误的放在S1和S2之间。

如果是形式2,维护者就不可能犯这种错误:

Messagemsg=getMessage();

好,现在我们来看性能,形式2真的相对形式1性能更差吗?对于下面的代码:

#includestdio.h

structMessage

Message()

{

printf("Message::Message()iscalled\n");

}

Message(constMessage)

{

printf("Message::Message(constMessagemsg)iscalled\n");

}

Messageoperator=(constMessage)

{

printf("Message::operator=(constMessage)iscalled\n");

}

~Message()

{

printf("Message::~Message()iscalled\n");

}

inta;

intb;

intc;

intd;

inte;

intf;

MessagegetMessage()

Messageresult;

result.a=0x11111111;

returnresult;

intmain()

Messagemsg=getMessage();

return0;

}

你认为运行时会输出什么呢?是不是这样:

Message::Message()iscalled

Message::Message(constMessagemsg)iscalled

Message::~Message()iscalled

Message::~Message()iscalled

并没有像预期的输出那样。

如果使用MSVC2017编译,且关闭优化(/Od),确实可以得到预期输入,但是一旦打开优化(/O2),输出就和GCC的一样了。

我们看看实际上生成了什么代码(使用GCC编译):

(gdb)disassemblemain

Dumpofassemblercodeforfunctionmain():

0x0000000000000776+0:

push

%rbp

0x0000000000000777+1:

mov

%rsp,%rbp

0x000000000000077a+4:

push

%rbx

0x000000000000077b+5:

sub

$0x28,%rsp

0x000000000000077f+9:

mov

%fs:0x28,%rax

0x0000000000000788+18:

mov

%rax,-0x18(%rbp)

0x000000000000078c+22:

xor

%eax,%eax

0x000000000000078e+24:

lea

-0x30(%rbp),%rax

#将栈上地址-0x30(%rbp)传给getMessage函数

0x0000000000000792+28:

mov

%rax,%rdi

0x0000000000000795+31:

callq

0x72agetMessage()

0x000000000000079a+36:

mov

$0x0,%ebx

0x000000000000079f+41:

lea

-0x30(%rbp),%rax

0x00000000000007a3+45:

mov

%rax,%rdi

0x00000000000007a6+48:

callq

0x7e4Message::~Message()

0x00000000000007ab+53:

mov

%ebx,%eax

0x00000000000007ad+55:

mov

-0x18(%rbp),%rdx

0x00000000000007b1+59:

xor

%fs:0x28,%rdx

0x00000000000007ba+68:

je

0x7c1main()+75

0x00000000000007bc+70:

callq

0x5f0__stack_chk_fail@plt

0x00000000000007c1+75:

add

$0x28,%rsp

0x00000000000007c5+79:

pop

%rbx

0x00000000000007c6+80:

pop

%rbp

0x00000000000007c7+81:

retq

Endofassemblerdump.

(gdb)disassemblegetMessage

DumpofassemblercodeforfunctiongetMessage():

0x000000000000072a+0:

push

%rbp

0x000000000000072b+1:

mov

%rsp,%rbp

0x000000000000072e+4:

sub

$0x20,%rsp

0x0000000000000732+8:

mov

%rdi,-0x18(%rbp)

#将main函数传入的栈上地址保存到-0x18(%rbp)处

0x0000000000000736+12:

mov

%fs:0x28,%rax

0x000000000000073f+21:

mov

%rax,-0x8(%rbp)

0x0000000000000743+25:

xor

%eax,%eax

0x0000000000000745+27:

mov

-0x18(%rbp),%rax

#将main函数传入的栈上地址传给Message::Message()函数

0x0000000000000749+31:

mov

%rax,%rdi

0x000000000000074c+34:

callq

0x7c8Message::Message()

0x0000000000000751+39:

mov

-0x18(%rbp),%rax

0x0000000000000755+43:

movl

$0x11111111,(%rax)

0x000000000000075b+49:

nop

0x000000000000075c+50:

mov

-0x18(%rbp),%rax

0x0000000000000760+54:

mov

-0x8(%rbp),%rdx

0x0000000000000764+58:

xor

%fs:0x28,%rdx

0x000000000000076d+67:

je

0x774getMessage()+74

0x000000000000076f+69:

callq

0x5f0__stack_chk_fail@plt

0x0000000000000774+74:

leaveq

0x0000000000000775+75:

retq

Endofassemblerdump.

可以看出来,在getMessage函数中构造的对象实际上位于main函数的栈帧上,并没有额外构造一个Message对象。这是因为开启了所谓的返回值优化(RVO,ReturnValueOptimization)的缘故。你想得到的效果编译器已经自动帮你完成了,你不必再牺牲什么。

RVO

对于我们这些用户来说,RVO并不是什么特别复杂的机制,主流的GCC和MSVC均支持,也没什么特别需要注意的地方。它存在的目的是优化掉不必要的拷贝复制函数的调用,即使拷贝复制函数有什么副作用,例如上面代码中的打印语句,这可能是唯一需要注意的地方了。从上面的汇编代码中可以看出来,在GCC中,其基本手段是直接将返回的对象构造在调用者栈帧上,这样调用者就可以直接访问这个对象而不必复制。

RVO是有限制条件的,在某些情况下无法进行优化,在一篇关于MSVC2005的RVO技术的文章中,提到了3点导致无法优化的情况:

1.函数抛异常

关于这点,我是有疑问的。文章中说如果函数抛异常,开不开RVO结果都一样。如果函数抛异常,无法正常的返回,我当然不会要求编译器去做RVO了。

2.函数可能返回具有不同变量名的对象

MessagegetMessage_NoRVO1(intin)

Messagemsg1;

msg1.a=1;

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论