在perl中,将子程序的return值赋给变量时,内存中的数据是否重复?

In perl, when assigning a subroutine's return value to a variable, is the data duplicated in memory?

sub foo {
    my @return_value = (1, 2);
}
my @receiver = foo();

这个赋值和 perl 中的任何其他赋值一样吗?数组在内存中重复?我怀疑这个原因,因为子程序持有的数组是一次性的,重复是完全多余的。出于优化原因,仅 'link' 数组到 @receiver 是有意义的。

顺便说一句,我注意到了一个类似的问题Perl: function returns reference or copy?,但没有得到我想要的。

我说的是 Perl5

ps。有关于 perl 这类主题的书籍或资料吗?

如果您没有明确指定 return,则子例程 return 是上次操作的结果。

@return_value@receiver 分开创建,当 @return_value 在子例程退出时超出范围时,将复制值并释放 @return_value 使用的内存。

是的 - 使用的内存是重复的。

如果你非常想避免这种情况,你可以创建一个匿名数组,然后 'pass' 引用它:

#!/usr/bin/env perl
use strict;
use warnings;

use Data::Dumper;

sub foo {
    my $anon_array_ref = [ 1, 2 ];
    return $anon_array_ref; 
}

my $results_from_foo = foo(); 

print Dumper $results_from_foo;

尽管这通常是过早的优化,除非您知道您正在处理非常大的数据结构。

注意 - 您可能应该在分配后在您的 sub 中包含一个明确的 return;,因为这是一个很好的做法,可以清楚地说明您在做什么。

return由 :lvalue 子编辑的标量未被复制。

XS 潜艇 return 编辑的标量未被复制。

不复制由函数(命名运算符)编辑的标量 return。

其他订阅者编辑的标量return被复制。

但那是在任何作业发挥作用之前。如果您将 returned 值分配给一个变量,您将复制它们(同样,在普通 Perl sub 的情况下)。

这意味着 my $y = sub { $x }->(); 复制 $x 两次!

但由于优化,这并不重要。


让我们从不复制它们的情况开始。

$ perl -le'
    sub f :lvalue { my $x = 123; print $x; $x }
    my $r = \f();
    print $r;
'
SCALAR(0x465eb48)  # $x
SCALAR(0x465eb48)  # The scalar on the stack

但是如果你删除 :lvalue...

$ perl -le'
    sub f { my $x = 123; print $x; $x }
    my $r = \f();
    print $r;
'
SCALAR(0x17d0918)  # $x
SCALAR(0x17b1ec0)  # The scalar on the stack

更糟糕的是,通常会通过将标量分配给变量来跟进,因此会发生第二个副本。

$ perl -le'
    sub f { my $x = 123; print $x; $x }
    my $r = \f();   # \
    print $r;       #  > my $y = f();
    my $y = $$r;    # /
    print $y;
'
SCALAR(0x1802958)  # $x
SCALAR(0x17e3eb0)  # The scalar on the stack
SCALAR(0x18028f8)  # $y

从好的方面来说,优化了赋值以最小化复制字符串的成本。

XS 子程序和函数(命名运算符)通常是 return 凡人 ("TEMP") 标量。这些是标量 "on death row"。如果没有人介入声明对它们的引用,它们将自动销毁。

在旧版本的 Perl (<5.20) 中,将一个凡人字符串分配给另一个标量将导致转移字符串缓冲区的所有权以避免必须复制字符串缓冲区。例如,my $y = lc($x); 不会复制 lc 创建的字符串;只是复制字符串指针。

$ perl -MDevel::Peek -e'my $s = "abc"; Dump($s); $s = lc($s); Dump($s);'
SV = PV(0x1705840) at 0x1723768
  REFCNT = 1
  FLAGS = (PADMY,POK,IsCOW,pPOK)
  PV = 0x172d4c0 "abc"[=13=]
  CUR = 3
  LEN = 10
  COW_REFCNT = 1
SV = PV(0x1705840) at 0x1723768
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0x1730070 "abc"[=13=]     <-- Note the change of address from stealing
  CUR = 3                        the buffer from the scalar returned by lc.
  LEN = 10

在较新版本的 Perl (≥5.20) 中,赋值运算符从不[1] 复制字符串缓冲区。相反,较新版本的 Perl 使用写时复制 ("COW") 机制。

$ perl -MDevel::Peek -e'my $x = "abc"; my $y = $x; Dump($x); Dump($y);'
SV = PV(0x26b0530) at 0x26ce230
  REFCNT = 1
  FLAGS = (POK,IsCOW,pPOK)
  PV = 0x26d68a0 "abc"[=14=]            <----+
  CUR = 3                                |
  LEN = 10                               |
  COW_REFCNT = 2                         +-- Same buffer (0x26d68a0)
SV = PV(0x26b05c0) at 0x26ce248          |
  REFCNT = 1                             |
  FLAGS = (POK,IsCOW,pPOK)               |
  PV = 0x26d68a0 "abc"[=14=]            <----+
  CUR = 3
  LEN = 10
  COW_REFCNT = 2

好的,到目前为止,我只谈到了标量。嗯,那是因为 subs 和函数只能 return 标量 [2].

在您的示例中,分配给 @return_value 的标量将被 returned[3]、复制,然后再次复制到 @receiver 通过分配。

您可以通过 return 对数组的引用来避免所有这些。

sub f { my @fizbobs = ...; \@fizbobs }
my $fizbobs = f();

那里唯一复制的是引用,最简单的非未定义标量。


  1. 好吧,也许永远不会。我认为字符串缓冲区中需要有一个空闲字节来保存 COW 计数。

  2. 在列表上下文中,它们可以 return 0 个、1 个或多个,但它们只能 return 个标量。

  3. 你的 sub 的最后一个运算符是一个列表赋值运算符。在列表上下文中,列表赋值运算符 return 是其左侧 (LHS) 计算的标量。有关详细信息,请参阅 Scalar vs List Assignment Operator