iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++中stack的pop()函数返回值解析
  • 769
分享到

C++中stack的pop()函数返回值解析

2024-04-02 19:04:59 769人浏览 安东尼
摘要

目录stack的pop()函数返回值全部demo分析c++的返回值优化从函数返回值RVOstack的pop()函数返回值 int temp = s.pop(); co

stack的pop()函数返回值

    int temp = s.pop();
    cout<<temp<<endl; 

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

全部demo

#include <iOStream>
#include <stack>
 
using namespace std;
 
int main()
{
	stack<int> s;
	if(s.empty())
		cout<<"empty"<<endl;   //empty
	s.push(1);
	s.push(6);
	s.push(66);
	cout<<s.size()<<endl;   //3
	int temp = s.pop();
	cout<<temp<<endl;	//66
	cout<<s.size()<<endl;	//2
	cout<<s.top()<<endl;	//6
	cout<<s.size()<<endl;	//2
	system("pause");
	return 0;
 
}

分析

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

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

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

C++的返回值优化

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

从函数返回值

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

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

首先定义Message类:

struct Message
{
    int a;
    int b;
    int c;
    int d;
    int e;
    int f;
};

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

void getMessage(Message &msg); // 形式1

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

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

Message getMessage(); // 形式2

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

1. 可读性变差

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

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

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

Message msg;     // S1
getMessage(msg); // S2

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

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

Message msg = getMessage();

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

#include <stdio.h>
 
struct Message
{
    Message()
    { 
        printf("Message::Message() is called\n"); 
    }
    Message(const Message &)
    {
        printf("Message::Message(const Message &msg) is called\n");
    }
    Message& operator=(const Message &)
    {
        printf("Message::operator=(const Message &) is called\n");
    }
    ~Message()
    {
        printf("Message::~Message() is called\n");
    }
    int a;
    int b;
    int c;
    int d;
    int e;
    int f;
};
 
Message getMessage()
{
    Message result;
    result.a = 0x11111111;
 
    return result;
}
 
int main()
{
    Message msg = getMessage();
    return 0;
}

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

Message::Message() is called
Message::Message(const Message &msg) is called
Message::~Message() is called
Message::~Message() is called

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

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

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

(gdb) disassemble main
Dump of assembler code for function main():
   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  0x72a <getMessage()>
   0x000000000000079a <+36>:    mov    $0x0,%ebx
   0x000000000000079f <+41>:    lea    -0x30(%rbp),%rax
   0x00000000000007a3 <+45>:    mov    %rax,%rdi
   0x00000000000007a6 <+48>:    callq  0x7e4 <Message::~Message()>
   0x00000000000007ab <+53>:    mov    %ebx,%eax
   0x00000000000007ad <+55>:    mov    -0x18(%rbp),%rdx
   0x00000000000007b1 <+59>:    xor    %fs:0x28,%rdx
   0x00000000000007ba <+68>:    je     0x7c1 <main()+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   
End of assembler dump.
(gdb) disassemble getMessage 
Dump of assembler code for function getMessage():
   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  0x7c8 <Message::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     0x774 <getMessage()+74>
   0x000000000000076f <+69>:    callq  0x5f0 <__stack_chk_fail@plt>
   0x0000000000000774 <+74>:    leaveq 
   0x0000000000000775 <+75>:    retq   
End of assembler dump.

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

RVO

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

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

1. 函数抛异常

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

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

Message getMessage_NoRVO1(int in)
{
    Message msg1;
    msg1.a = 1;
 
    Message msg2;
    msg2.a = 2;
 
    if (in % 2)
    {
        return msg1;
    }
    else
    {
        return msg2;
    }
}

经过验证,在GCC上确实也是这样的,拷贝构造函数被调用了。但这种情况在很多时候应该都是可以通过重构避免的。

Message::Message() is called
Message::Message() is called
Message::Message(const Message &msg) is called
Message::~Message() is called
Message::~Message() is called
Message::~Message() is called

3. 函数有多个出口

Message getMessage_NoRVO2(int in)
{
    Message msg;
    if (in % 2)
    {
        return msg;
    }
    msg.a = 1;
    return msg;
}

这个在GCC上验证发现RVO仍然生效,查看汇编发现只有一个retq指令,多个出口被优化成一个了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: C++中stack的pop()函数返回值解析

本文链接: https://www.lsjlt.com/news/165169.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++中stack的pop()函数返回值解析
    目录stack的pop()函数返回值全部demo分析C++的返回值优化从函数返回值RVOstack的pop()函数返回值 int temp = s.pop(); co...
    99+
    2022-11-13
  • C++中的函数返回值问题
    目录1、返回值2、指针类型的函数——返回指针3、返回引用4、综合示例首先,强调一点,和函数传参一样,函数返回时也会做一个拷贝。 从某种角度上看,和传参一样,也...
    99+
    2022-11-13
  • C语言中返回值指针函数的示例分析
    这篇文章主要为大家展示了“C语言中返回值指针函数的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C语言中返回值指针函数的示例分析”这篇文章吧。   #inc...
    99+
    2023-06-29
  • C语言返回值指针的函数详解
          #include<stdio.h> void main() { int a[5] = { 1,3,5,7,9 }; ...
    99+
    2022-11-13
  • C/C++ 引用作为函数的返回值方式
    目录case1:用返回值方式调用函数case2:用函数的返回值初始化引用的方式调用函数case3:用返回引用的方式调用函数case4:用函数返回的引用作为新引用的初始化值的方式来调用...
    99+
    2022-11-13
  • 通过实例详解C语言函数返回值
    目录前言C语言返回值c语言函数调用后必须带回返回值总结前言 函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。 return 语句...
    99+
    2022-11-13
  • C语言函数返回值与参数传递实例分析
    本篇内容介绍了“C语言函数返回值与参数传递实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一:参数传...
    99+
    2022-10-19
  • C# 函数返回多个值的方法详情
    目录引言1.使用ref参数2.使用out参数修饰符3. 使用元组类4.使用C#7 ValueTuple5. 使用结构或类引言 根据 C# 语言规范,不可能从一个方法返回多个值。使用 ...
    99+
    2022-11-13
  • C#函数out多个返回值问题怎么解决
    今天小编给大家分享一下C#函数out多个返回值问题怎么解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。C#函数返回多个参数...
    99+
    2023-07-05
  • C语言中返回值的示例分析
    这篇文章给大家分享的是有关C语言中返回值的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。 函数返回值定义的结构在<cstdlib>,其中有两个成员。为 di...
    99+
    2022-10-19
  • python中函数的返回值及类型详解
    目录1.返回值介绍2.带有返回值的函数3.保存函数的返回值4.四种函数的类型1.无参数,无返回值的函数2.无参数,有返回值的函数3.有参数,无返回值的函数4.有参数,有返回值的函数5...
    99+
    2023-05-14
    python函数返回值 python 返回值类型
  • C语言中函数返回值不一致问题
    目录C语言函数返回值不一致函数的返回值注意事项函数的返回值注意事项总结C语言函数返回值不一致 在运行成程序上有时会发现函数内部的值与返回到主函数的值会相差很多出现随机值,但是它们的地...
    99+
    2023-02-24
    C语言函数 函数返回值不一致 C语言函数返回值
  • vue中then后的返回值解析
    目录then后的返回值获取.then()中的返回值解决方法如下调用此方法then后的返回值 Promise 中处理的是异步调用,异步调用是非阻塞式的,在调用的时候并不知道它什么时候结...
    99+
    2022-11-13
  • C语言中函数返回值不一致问题如何解决
    本文小编为大家详细介绍“C语言中函数返回值不一致问题如何解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“C语言中函数返回值不一致问题如何解决”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。C语言函数返回值不一致...
    99+
    2023-07-05
  • vue parseHTML 函数拿到返回值后的处理源码解析
    目录引言parseStartTag函数返回值handleStartTag源码tagName 及unarySlash调用parser钩子函数引言 继上篇文章: parseHTML 函数...
    99+
    2022-09-27
  • Golang函数的命名返回值专题详解
    Golang作为一门向并发领域发展的编程语言,为了让开发者更加方便地处理数据和状态,设计了一些非常方便实用的特性和语法,其中包括函数的命名返回值。在本文中,我们将深入探讨这个特性的使用方法和注意事项。一、命名返回值的概念在函数中,我们经常需...
    99+
    2023-05-16
    函数 Golang 命名返回值
  • python中函数的返回值及类型实例代码分析
    这篇“python中函数的返回值及类型实例代码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“python中函数的返回值及...
    99+
    2023-07-05
  • python的函数形参和返回值你了解吗
    目录函数的返回值函数的参数不可变参数和可变参数+=函数的参数缺省参数多值参数元组和字典的拆包总结函数的返回值 一个函数执行后可以返回多个返回值 def measure(): ...
    99+
    2022-11-13
  • python中函数返回值的作用是什么
    python中函数返回值的作用是什么?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。python可以做什么Python是一种编程语言,内置了许多有效的工具,Pyt...
    99+
    2023-06-14
  • 如何解决Shell中函数返回值超出问题
    这篇文章主要介绍“如何解决Shell中函数返回值超出问题”,在日常操作中,相信很多人在如何解决Shell中函数返回值超出问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何解决Shell中函数返回值超出问题...
    99+
    2023-06-09
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作