C++中函数返回的过程是怎样的?

 2024-02-24 00:03:11  阅读 0

C++中函数返回的过程(不考虑异常和其他异常退出方法)是一个稍微长的话题...开始阶段你可能不需要担心它。 此代码不需要无名临时对象,也不分配值。

总结

通常你只需要正常编写函数即可,返回相同返回类型的局部变量时无需移动,并尽可能避免悬空指针/引用。 大多数时候您不需要了解后面描述的细节。

如果要返回指针/引用,尽量让它引用在函数外部创建且函数退出时不会过期的对象,或者函数体内的对象; 它也可以被返回以指示某种失败。

特殊情况包括优化调用约定的返回类型设计、抑制NRVO进行优化等,这些都是相当高级甚至在业务中很少见的内容。

标准语义部分

首先初始化返回值。 这里所做的事情与返回类型有关:

初始化返回值后,操作如下:

语句中生成的临时对象按照与创建相反的顺序销毁; 其他函数体中的局部变量(非 ORed)将根据离开作用域的通常规则被销毁。 (这里的“销毁”一词并不是很严格,对于非类类型,可以认为该对象已经不存在了。)

在标准语义中,控制权离开了函数,函数调用表达式的类型和值类别确定如下:

用于传递/返回目的的简单类型

即使从 C++17 开始,如果函数返回类型是满足以下条件的类类型,则它在语义上可以在返回时具有临时对象:

形参对象的销毁

根据标准,如果形式参数是具有非平凡析构函数的类类型对象(注意它不是引用),则实现选择两种策略之一。 具体选择是实现定义的:

这两种策略之间存在一些微妙的差异,前者在更多情况下留下悬空引用或悬空指针。 但是,无论如何,尽量不要使函数参数成为对象类型,并让返回的引用或指针引用参数对象。

函数返回值_构造方法返回值_c thread 返回值

虽然形式参数对象在标准中离开函数后被破坏,但选择前者的实现(例如 MSVC)可能会在汇编/机器代码级别上离开被调用函数之前破坏。

实现和调用约定

在实现中,当调用没有内联的函数时,往往需要在栈上记录调用该函数后控制流返回的位置。 当函数正常返回时,控制流会获取堆栈上记录的地址,并跳转到该地址进行后续操作。

除了这个公共部分之外,函数返回的实现取决于调用约定。 各个平台的调用约定非常复杂,但是我们可以将返回行为总结为两部分:返回方法和堆栈清理。

通过寄存器或内存返回

大多数调用约定将允许使用一个或多个通用寄存器来返回,并且对不长于通用寄存器的整数的引用和指针将通过寄存器返回。 浮点类型可以通过专门的浮点寄存器返回。

在某些调用约定下,如果类类型的大小和组成满足某些要求,则可以通过寄存器返回对于传递/返回目的来说很简单的类类型。

c thread 返回值_函数返回值_构造方法返回值

对于尺寸较大的类类型,或者其返回涉及不平凡的特殊成员函数(以及某些编译器提供的较大的非标准类型),调用约定将选择通过内存返回(某些调用约定要求任何class类型必须通过返回),具体方法为:

调用函数时,首先确定返回对象的地址,并将其作为隐藏参数传递给函数。 初始化返回值时,初始化该返回地址处的对象。 (非必须)为了方便链式调用,在离开函数时,该地址也被存储在一个特殊的通用寄存器中。

事实上,通过内存返回的机制构成了允许函数返回而不生成临时对象,以及允许编译器使局部变量和函数返回值最终形成同一个对象的基础——前者需要继续向内传递最终对象的地址。 ,这需要在用于返回的地址处创建一个局部变量。

堆栈清理

除了控制流返回的位置之外,调用约定还需要通过寄存器和堆栈传递参数。 有些调用约定仅通过堆栈,而其他调用约定仅在没有足够的寄存器来传递参数时才通过堆栈。 无论调用约定如何,在离开函数后都需要将堆栈记录返回到其原始位置,通常是将堆栈指针恢复到其原始值。

不同的调用约定在堆栈清理方面的行为不同:

默认调用约定(例如 x64 调用约定)通常由调用者清除堆栈。 这样的设计对于C的变长实参函数来说更加合理(例如,注意它与C++的形参包无关),并且还允许在完整表达式结束时销毁C++中的形参对象。

在其他调用约定(例如 32 位调用约定)中,堆栈由被调用者清除。 这种设计不太可能与变长参数函数兼容(例如,不支持),并且也使得这种调用约定无法允许在完整表达式末尾销毁形参对象。 清栈的目的可能是为了减少机器代码大小:清栈的机器代码只需要在被调用函数中存在一次,而不必到处重复。

参考C++17,如果返回类型是类类型,这一步也会产生一个纯右值,但语义上总是有一个临时对象。 具有自动保存期限。 此操作称为“命名返回值优化(NRVO)”。 尽管它通常被称为“优化”,但这种优化会跳过构造函数和析构函数调用,从而可能改变标准意义上的可观察行为。 无论是否执行 NRVO,都需要可调用适当的构造函数。 C++17 预类类型纯右值必须是立即临时对象。 当操作数是同一类类型的纯右值临时对象(也忽略 const)时,直接将临时对象转换为函数返回的临时对象的操作也可能会改变可观察的行为。 这个操作称为“返回值优化(value,RVO)”。 这种类型的变量称为“隐式可移动实体( )”。 在适用于 C++11 的错误报告中放宽了对隐式可移动实体的要求,以前(从 C++11 开始)它必须是一个具有与返回类型相同类型的自动存储持续时间的局部变量(忽略顶级常量)。 {args} 该表达式调用构造函数,但它不是函数调用表达式。 用标准说法来说,表达式的类型不能是引用类型,但作为返回类型的引用会影响函数调用表达式的值类别。 C++17/20中对此的规定是有缺陷的,尚未解决,参见CWG 2434。这里的标准词是(),与有些不同,例如不考虑访问权限。 这意味着这些构造函数将执行逐位复制,但具体要求更加严格。 这里的要求与“ ()”类型的要求非常相似,但不考虑赋值运算符。 - 某些调用约定(例如 MS x64 调用约定)具有更严格的限制。 组合要求的一个示例是 -V ABI 。

标签: 返回 调用 函数

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码