在perl中,当将子例程的返回值赋给变量时,数据是否在内存中重复?

nic*_*jou 5 perl programming-languages

sub foo {
    my @return_value = (1, 2);
}
my @receiver = foo();
Run Code Online (Sandbox Code Playgroud)

这个分配是否像perl中的任何其他分配一样?数组在内存中重复?我怀疑这个原因,因为子程序保存的数组是一次性的,复制完全是多余的.为了优化原因,将数组"链接"到@receiver是有意义的.

顺便说一句,我注意到一个类似的问题Perl:函数返回引用还是复制?但没有得到我想要的.

而我在谈论Perl5

PS.关于perl这类主题的任何书籍或材料?

ike*_*ami 6

sub返回的标量:lvalue不会被复制.

XS subs返回的标量不会被复制.

函数返回的标量(命名运算符)不会被复制.

其他潜艇返回的标量被复制.

但那是在任何任务发挥作用之前.如果将返回的值分配给变量,则将复制它们(同样,在普通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
Run Code Online (Sandbox Code Playgroud)

但如果你删除: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
Run Code Online (Sandbox Code Playgroud)

更糟糕的是,人们通常会通过将标量指定给变量来进行跟进,因此会出现第二个副本.

$ 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
Run Code Online (Sandbox Code Playgroud)

从好的方面来说,优化分配以最小化复制字符串的成本.

XS subs和函数(命名运算符)通常返回凡人("TEMP")标量.这些是"死囚牢房"中的标量.如果没有任何步骤声明对它们的引用,它们将被自动销毁.

在旧版本的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"\0
  CUR = 3
  LEN = 10
  COW_REFCNT = 1
SV = PV(0x1705840) at 0x1723768
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0x1730070 "abc"\0     <-- Note the change of address from stealing
  CUR = 3                        the buffer from the scalar returned by lc.
  LEN = 10
Run Code Online (Sandbox Code Playgroud)

在较新版本的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"\0            <----+
  CUR = 3                                |
  LEN = 10                               |
  COW_REFCNT = 2                         +-- Same buffer (0x26d68a0)
SV = PV(0x26b05c0) at 0x26ce248          |
  REFCNT = 1                             |
  FLAGS = (POK,IsCOW,pPOK)               |
  PV = 0x26d68a0 "abc"\0            <----+
  CUR = 3
  LEN = 10
  COW_REFCNT = 2
Run Code Online (Sandbox Code Playgroud)

好吧,到目前为止,我只讨论过标量.嗯,这是因为subs和函数只能返回标量[2].

在您的示例中,分配给的标量@return_value将被返回[3],复制,然后@receiver由赋值再次复制.

您可以通过返回对数组的引用来避免所有这些.

sub f { my @fizbobs = ...; \@fizbobs }
my $fizbobs = f();
Run Code Online (Sandbox Code Playgroud)

复制的唯一东西是引用,最简单的非undefined标量.


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

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

  3. sub的最后一个运算符是列表赋值运算符.在列表上下文中,列表赋值运算符返回其左侧(LHS)计算的标量.有关详细信息,请参阅标量与列表分配运算符.

  • 所以,这是一个答案,向我保证,我根本不会**知道perl.:) Hovever,我喜欢这里有这样的答案,因为它们可以帮助我理解深层次的东西(几年后):) (3认同)