C++初学者指南第一步---14.函数调用机制

2024-06-24 03:36

本文主要是介绍C++初学者指南第一步---14.函数调用机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C++初学者指南第一步—14.函数调用机制

文章目录

  • C++初学者指南第一步---14.函数调用机制
    • 1.记住:内存的结构
    • 2.函数调用是如何工作的
    • 3. 不要引用局部变量
    • 4. 常见编译器优化
    • 5. Inlining内联

1.记住:内存的结构

在这里插入图片描述
堆(自由存储)

  • 用于动态存储期对象,例如 std::vector 的内容。
  • 空间大,可以用于大容量存储(大多数用于主内存)。
  • 可以根据需要分配和释放任何对象。
  • 按照无特定顺序的分配(释放) ⇒ 碎片化。
  • 分配速度慢:需要为新对象找到连续未被占用的空间。

在这里插入图片描述

  • 自动存储期对象使用:局部变量,函数参数等。
  • 空间小(通常只有几兆(M)字节)。
  • 分配速度快:新对象总是放在栈顶部。
  • 对象按它们创建的相反顺序被释放。
  • 无法释放顶端(= 最新的)以下的对象。

2.函数调用是如何工作的

<1>该示例假定没有编译器优化,例如内联(用函数体替换函数调用),返回类型优化等。
此外,在函数调用时放入栈的确切顺序(调用约定)取决于平台(CPU 架构 + 操作系统 + 编译器)。
在这里插入图片描述
<2>程序开始。
在这里插入图片描述
<3>局部变量 y 被放入栈。
在这里插入图片描述
<4> 局部变量 i 被放入栈。
在这里插入图片描述
<5> 函数的返回值占位符被放入堆栈
在这里插入图片描述
<6> 当前指令的内存地址被放在栈上,这样在离开被调用的函数后,我们就知道从哪里恢复程序。
在这里插入图片描述
<7> 帧指针标记了当前函数的栈帧的开始。在当前栈帧内的一切都将被视为函数局部的。需要帧指针是因为不同的函数调用可能有不同大小的栈帧。
在这里插入图片描述
<8> 执行跳转到函数square的内存地址。
在这里插入图片描述
<9> 函数参数p放在栈上,它的值由调用参数(y的值)决定。
注意:返回地址、占位符、局部参数等放在栈上的顺序取决于平台的调用约定(CPU体系结构+ OS +编译器)。
在这里插入图片描述
<10> 函数局部变量 x 放到栈上。
在这里插入图片描述
<11> 表达式 p * p 的结果被赋给 x。
在这里插入图片描述
<12> 语句return x,将 x 的值复制到 返回值占位符。
在这里插入图片描述
<13>离开函数square时: 堆栈的顶部位置减少到堆栈帧下方;这意味着所有函数局部变量都从堆栈中弹出。
在这里插入图片描述
<14> 执行通过跳转到之前存储的返回地址返回到调用位置。
在这里插入图片描述
<15> 赋值语句int i = …会导致返回值被复制到i中。
在这里插入图片描述
<16> square函数的返回值被从栈中弹出。
在这里插入图片描述
<17> 局部变量k被放入栈。
在这里插入图片描述
<18> 程序结束,所有关联的变量都会从栈中弹出。
在这里插入图片描述

3. 不要引用局部变量

如果我们把返回类型改为int&会怎么样呢?
<1>
在这里插入图片描述
<2> 在从square返回之前栈内容:

  • 函数局部变量x
  • 函数参数p
  • 函数调用后的下一条指令的地址
  • square返回值的占位符
  • main函数的局部变量 y 和 i
    在这里插入图片描述
    <3> 语句return x;将 x 的地址复制到 返回值占位符。
    在这里插入图片描述
    <4> 离开函数square: 栈的顶部位置降低到栈帧下方; 这意味着所有square函数的局部变量都会从栈中弹出。
    通过跳转到先前存储的返回地址,执行流程回到调用位置。
    在这里插入图片描述
    <5> 赋值 int& i = … 会导致返回值(一个整数的内存地址)被复制到引用 i& 中。
    x的内存位置实际上在栈的当前顶部位置之上。任何后续的栈分配都会导致它被其他值覆盖。
    这将导致 => 未定义行为
    这样的程序在运行时行为是未定义的/非确定性的,因为它有时可能会工作(如果 x 的内存没有被覆写)有时可能不会。
    在这里插入图片描述

4. 常见编译器优化

现代的 C++ 编译器进行多项优化(尤其是在较高的优化级别 -O2 和 -O3),使函数调用速度更快。
Return Value Optimization 返回值优化 (RVO)

  • 适用于类似:return Type{}; 或 return Type{argument,…}; 这样的语句。
  • 不会分配额外的占位符用于返回值,也不会进行复制。相反,外部对象 res 将直接在调用位置构造。
  • 这种优化是强制的,即在 C++17 版本中必定会执行。
Point foo (…) { …return Point{…};
}
Point res = foo();

Named Return Value Optimization 命名返回值优化 (NRVO)

  • 适用于类似: return local_variable; 这样的语句。
  • 不会分配额外的占位符用于返回值,也不会进行复制。相反,本地对象 loc 和外部对象 res 被视为同一个对象。这样在调用点仅会发生一次分配。
  • 这种优化不是必需的,但几乎所有现代编译器都会尽可能地执行它。
Point foo (…) {Point loc;…return loc;
}
Point res = foo();

5. Inlining内联

调用小/短函数的地方被该函数的代码替换。
在这里插入图片描述
内联只会发生在编译器“看到”函数声明的同时也看到它的完整定义,如果我们分别编译程序的不同部分,这种情况就不一定会发生(更多内容请参考《分离编译》章节)。
这是 C++ 性能优势的一个来源。在许多其他语言(比如 Java、C# 等)中,内联化要困难得多,有时甚至是不可能的。这些语言通常具有始终开启的多态性,这意味着所有/大多数函数/方法调用只能在运行时解析。

附上原文链接
如果文章对您有用,请随手点个赞,谢谢!^_^

这篇关于C++初学者指南第一步---14.函数调用机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089064

相关文章

电脑密码怎么设置? 一文读懂电脑密码的详细指南

《电脑密码怎么设置?一文读懂电脑密码的详细指南》为了保护个人隐私和数据安全,设置电脑密码显得尤为重要,那么,如何在电脑上设置密码呢?详细请看下文介绍... 设置电脑密码是保护个人隐私、数据安全以及系统安全的重要措施,下面以Windows 11系统为例,跟大家分享一下设置电脑密码的具体办php法。Windo

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

MySQL中的锁和MVCC机制解读

《MySQL中的锁和MVCC机制解读》MySQL事务、锁和MVCC机制是确保数据库操作原子性、一致性和隔离性的关键,事务必须遵循ACID原则,锁的类型包括表级锁、行级锁和意向锁,MVCC通过非锁定读和... 目录mysql的锁和MVCC机制事务的概念与ACID特性锁的类型及其工作机制锁的粒度与性能影响多版本