【问题标题】:how does the function return in c++ workc++中的函数返回是如何工作的
【发布时间】:2012-03-06 02:23:27
【问题描述】:

如果我调用函数 f(3);,我想知道编译器如何保存这个临时 int;

int f (int x) { return x; } 

以及编译器将如何执行此操作:

int a=f(3);

是不是就像做 int a=x; (我知道 x 已经被销毁了)或者它确实创建了一个名为 f(3) 的临时变量,例如 int f(3)=x;

int& a=f(3);

为什么这不起作用?

【问题讨论】:

  • 您是在问优化编译器将如何处理此特定代码,还是关于一般机制?
  • 你不能做int& a=f(3),因为函数f返回一个整数并且你将它分配给一个整数的引用(int&)。
  • @prelic :将整数分配给对整数的引用是完全有效的。无效的是将临时分配给引用。
  • @MatthewFlaschen 我在说一般。
  • 这是一个不能取地址的变量,因为它会在语句返回后被销毁(为了简化)。如果您想将其永久存储在某处(即复制它),则需要对其进行一些存储。顾名思义,引用只是一个名称,因此您不能将函数的返回值绑定到引用。 (你可以使用 const 引用,但这可能会引发崩溃,不要这样做)。

标签: c++ function


【解决方案1】:

函数调用

编译器会做以下事情之一:

  1. 将参数压入堆栈并调用函数
    • 对于比您的功能更复杂的功能,通常会发生这种情况。
  2. 将参数加载到寄存器中并调用函数
    • 可能在优化时发生,并且有足够的寄存器来保存需要传递的变量。
  3. 完全优化函数(内联)
    • 对于像您这样的微不足道的函数,理智的编译器会以最基本的优化级别执行此操作,以便您获得与 int a = 3 相同的程序集。

C++ 中的引用变量

一个引用变量,在您的代码中声明为int &a 是“现有内存位置的不同名称”。所以声明int &a 不会在任何地方为int 分配空间。它只是声明a 来引用已经分配的内存位置。

这个位置可能是一个现有的变量int b,所以你说:

int b;
int &a = b;

这里,a 将引用 b 所引用的相同内容。 “现有对象的新名称”是一个很好的成语。

你可以说int &a = array[5],这样a 指的是int 数组array 的第6 个元素,或者int &a = *(int*)0x12345678 指的是一个特定的内存位置,但我离题了。

你的代码

int &a = 3;

不能工作,因为3是一个临时对象,语句执行后会被遗忘。要从根本上理解这个问题,可以这样想:如果a 指的是一个已经分配的内存位置,那么在int &a = 3 语句执行后,它会指什么,并且不再有一个临时对象@987654340 @?

这也是函数中引用变量的常见问题:返回对函数局部对象的引用是未定义的行为……但我又跑题了。您总是必须有一个“活的、已分配的对象”供a 引用,故事结束。

更深入地了解参考变量

这样的语句通常会发生什么

int a = 3;

是编译器生成代码到(简化):

  1. 将常数 3 加载到寄存器中
  2. 将寄存器加载到为 a 分配的内存位置

关键是:在任何一种情况下,都没有为对象3 分配长寿命内存位置,因此int &a 确实不能引用该对象。

“长期存在的内存位置”是指将在分配操作之后存在的位置。存储 3 的寄存器可能会在赋值操作后立即被覆盖和重用,因此它甚至在理论上也不符合 int &a 的目标(实际上,int &a 只能是用于引用 内存 位置,而不是寄存器)。

【讨论】:

  • 如果我有这个功能 > int& f(int& x){return x;} 和 > int a=1; > f(a) ;所以现在 f(a) 和其他变量一样吗?
  • 你能给我任何我可以理解函数返回如何工作的资源吗?非常感谢
  • 代码通常如何在这个级别上工作非常非常复杂。从你对 C++ 的理解来看,我建议查阅一些关于编译器构造和计算机体系结构的书籍,或者就这些主题学习一些大学课程。话虽如此,我找到了寄存器使用的快速参考,具体取决于编译器和操作系统here,但 x86 以外的架构会有不同的寄存器。
【解决方案2】:
  1. 这取决于调用约定。使用cdecl(很常见),函数将在EAX 寄存器中返回x。然后它将被复制到分配给a的寄存器中。

当然,优化编译器会将整个事情优化为:

int x = 3;

2。你不能引用对象生命周期已经结束的东西。 x 的对象生命周期在函数结束时结束。

【讨论】:

    【解决方案3】:

    任何合理的编译器都可以(并且将会)转向

    int a=f(3);
    

    进入

    int a=3;
    

    该调用最初会编译为将返回地址推入堆栈,然后是参数;函数本身将编译为弹出参数,弹出返回地址并再次推送参数,然后分支到返回地址。一个简单的优化器会检测到没有完成任何有用的工作,然后优化整个事情。

    【讨论】:

      【解决方案4】:
      1. 当您调用函数 f(3) 时,调用语句的指令地址会保存在寄存器中,并且您的指令指针会跳转到函数 f 的第一条语句的地址。函数 f 的新堆栈帧也被压入堆栈。当函数调用返回时,将为 f(3) 返回的 int 创建一个临时值,这就是当您执行 int x=f(3); 时将分配给 x 的值(因此 f 的返回值在一个临时值中创建,然后复制到x) 所以是的,正在为退货创建一个临时的。为 f(3) 创建的堆栈也被销毁。

      2. int& a=f(3); 不能作为参考。参考是别名。别名是已经存在的东西。 f(3) 返回要分配给变量的临时副本。由于堆栈将在 f(3) 调用后消失,因此您无法真正为其分配引用。

      【讨论】:

        【解决方案5】:

        int& a=f(3); - 这不好的是你正在创建一个对临时变量的引用。在函数退出时,引用所指的数据已被清除,因此它现在是一个挂起的引用。

        在您概述的情况下,编译器很可能只会将int a = f(3); 视为int a = 3;,但您永远无法确定,因为最终这取决于特定的编译器以及它如何执行优化。

        【讨论】:

          猜你喜欢
          • 2012-03-28
          • 2016-06-14
          • 1970-01-01
          • 1970-01-01
          • 2023-03-06
          • 1970-01-01
          • 1970-01-01
          • 2016-08-13
          • 1970-01-01
          相关资源
          最近更新 更多