【问题标题】:Function Return Mechanism:Temporary object, R-Value, L-Value函数返回机制:临时对象、R-Value、L-Value
【发布时间】:2020-01-02 16:35:53
【问题描述】:

从底层分析这个问题,当一个函数返回一个值时,它要么在 cpu 寄存器中返回,要么在调用者先前在堆栈上分配的空间中返回。 此时调用函数可以获取该值并将其复制到其局部变量中。

int sum(int a,int b){return a + b;}

int main(){int risultato = sum(10,20);return 0;}

在这种情况下,sum 函数返回 EAX 寄存器中的值。然后 main 函数将 eax 寄存器中的值复制到堆栈上的一个内存位置。

这就是真正发生的事情。

现在转到 C++ 的抽象,如果我尝试执行这样的操作: 总和 (10.20) = 4; 它给了我一个错误。 这是因为基本上该函数不是返回包含值的内存位置,而是返回值本身。 因此,作为一个 r 值,这将是不可分配的,因为不可能将一个值分配给另一个值。 当使用取消引用运算符 * 时,问题变得完全不同。 在这种情况下,它不会返回一个值,而是内存位置本身(左值),因此可以分配。

我写的对吗?

让我们现在来看第二个例子。

class class1 {public: int i; int b; class1(int i,int b) { this->i = i;this->b = b; }};

class1 fun() { class1 c(10,5); return c; }

int main() {fun().i = 4; return 0;}

在这种情况下,函数返回一个对象。 如果我尝试执行这样的指令: 乐趣 ()。我 = 4;我总是出错。

我知道在调用函数时会在堆栈上创建一个临时对象。 将函数返回一个对象,但不是作为变量(左值),而是作为一组值,不可能将其中一个赋值为 4。

这里的语句似乎也存在同样的问题:

class1(10,20).i = 4;

在这种情况下,我正在创建一个临时对象,我不明白为什么它不能让我分配对象的变量 i,为什么在这种情况下它总是被解释为 r 值而不是l 值? 我知道我所做的在实践中没有用,但这仍然是一个纯粹的理论问题,我需要正确理解语言的语法。

您能否评论我到目前为止所说的所有内容,表达您的观点并尝试回答最后一个问题?

【问题讨论】:

  • 然后 main 函数将 eax 寄存器中的值复制到堆栈上的内存位置。 或者不,如果优化编译器只是将risultato 保存在类似的寄存器中EAX。或者完全优化它,因为在这种情况下它没有被使用。在抽象 C 中,每个对象都有一个内存地址(register int foo 变量除外),但在实践中,除非您禁用优化变量,否则只有在编译器用完寄存器时才会有地址。

标签: c++ function assembly return-value


【解决方案1】:

现在转到 C++ 的抽象,如果我尝试执行这样的操作: sum (10.20) = 4;它给了我一个错误。这是因为该函数基本上不是返回包含值的内存位置,而是返回值本身。因此,作为一个 r 值,这将是不可分配的,因为不可能将一个值分配给另一个值。当使用取消引用运算符 * 时,问题变得完全不同。在这种情况下,它不会返回一个值,而是返回内存位置本身(左值),因此可以分配。

我写的对吗?

有点。你说

这是因为该函数基本上没有返回包含该值的内存位置

但事实并非如此。返回一个对象,该对象有一个值。使其成为右值的原因是该函数“按值返回”(另一个名称是创建一个临时对象)。

因此,作为一个右值,这将是不可分配的,因为不可能将一个值分配给另一个值

这仅适用于内置类型。内置类型的赋值运算符要求被赋值的对象是左值。如果您有用户定义的类型(classstruct),那么您可以分配给一个右值。

在这种情况下,我正在创建一个临时对象,我不明白为什么它不能让我分配对象的变量 i,为什么在这种情况下它总是被解释为 r 值而不是l 值?

原因是operator . 如果您调用它的对象是右值,那么您访问的成员将被视为右值。由于i 是一个内置类型,并且是一个右值,所以你不能分配给它。

【讨论】:

  • 我的意思是,在我调用函数的代码点,当返回一个 r 值时,就好像我有一个值而不是内存位置。这种理解有错吗?
  • @Tony92 C++ 抽象机没有这种区别。您按值返回了一个对象(创建了一个临时对象),所有临时对象都是右值。
  • 好的,非常感谢。因此,当我键入函数名称后跟圆括号时,我必须将该点视为临时对象。即使我使用地址运算符 & 也会发生同样的事情吗?在这种情况下,会返回一个指针(地址),它总是一个 r 值吗?
  • @Tony92 如果你通过引用返回,那么你就没有右值了。拥有int global = 42; int& foo() { return global; } foo() = 21; 是合法的,因为引用总是被认为是左值。听起来你真的可以使用good C++ book。 C++ 是一种非常复杂的语言,一本合适的书是学习它的最佳方式之一。
  • 是的,如果我返回一个引用,它是一个自动取消引用的指针,我会得到一个左值。但是如果函数返回一个指针,则返回一个右值而不是左值。对吗?
【解决方案2】:

我知道我所做的在实践中没有用

这是您问题的答案:

为什么在这种情况下它总是被解释为右值而不是左值?

如果编译器需要把它变成一个L-Value,那就更难实现了,而且因为它没有用,所以不值得这么麻烦。

有些东西只是为了方便编译器编写者。

【讨论】:

    【解决方案3】:

    @NathanOliver 回答了 C++ 抽象机部分。我将添加一条关于它如何映射到 asm 的注释。

    然后 main 函数将 eax 寄存器中的值复制到堆栈上的一个内存位置。

    如果优化编译器只是将risultato 保存在像 EAX 这样的寄存器中,则不然。或者完全优化它,因为在这种情况下它没有被使用。

    在抽象 C 中,每个对象都有一个内存地址(register int foo 变量除外),但在实践中,除非您禁用优化,否则只有在编译器用完寄存器时变量才具有地址。

    返回值对象在EAX中。

    请注意,主流 C++ 调用约定只会在寄存器中返回可简单复制的对象。一个重要的构造函数或析构函数将强制隐藏指针返回一个成员的结构,以确保构造函数和析构函数具有一致的this。 (调用约定规则不能依赖于构造函数和析构函数的内容,只依赖于是否定义。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-10
      • 1970-01-01
      • 2016-10-10
      • 2013-04-26
      相关资源
      最近更新 更多