1

因此,最近在尝试修补错误时,一位黑客同事告诉我,由于传递给子例程(方法)的字符串的值可能非常大,因此通过访问它$_[1]可以避免内存复制。但是,我认为传递给子例程的任何值@_首先被复制到?那么在下面的示例中,内存是否被复制了两次?还是我对传递给该方法时所做的副本有误?

sub foo {
    my $self = shift

    $_[0]    # access $str in @_ directly
    my ( $str ) = @_; # makes another copy of @_
}

sub bar {
    my $self = shift;
    my $str = 'something very large'; 

    $self->foo( $str ); #copies $str to the @_ of foo
}

这就是为什么我向作者建议允许通过标量 ref 传递,这将在传递给方法本身时避免复制(除了引用本身)。重申一下:将值传递给子例程是否意味着该值被复制到@_

4

3 回答 3

7

根据http://perldoc.perl.org/perlsub.html(重点是我的):

传入的任何参数都显示在数组中@_。因此,如果您调用带有两个参数的函数,它们将存储在$_[0]and中$_[1]该数组@_是一个本地数组,但它的元素是实际标量参数的别名。特别是,如果一个元素$_[0]被更新,相应的参数也会被更新(或者如果它不可更新,则会发生错误)。如果参数是调用函数时不存在的数组或散列元素,则该元素仅在(并且如果)它被修改或引用它时创建。(一些早期版本的 Perl 创建了该元素,无论该元素是否被分配。)分配给整个数组@_会删除该别名,并且不会更新任何参数。

根据我的阅读,这似乎表明,默认情况下,没有复制到@_.

虽然我承认使用的语言有点迟钝。

于 2012-09-21T18:44:19.660 回答
5

是的,其中的元素@_有别名。将参数传递到子例程中不会发生复制。

这意味着您可以做一些有用但令人惊讶的事情,例如:

sub strip {
    $_[0] =~ s{^\s+}{};
    $_[0] =~ s{\s+$}{};
}

my $var = "   foo   ";
strip($var);
print $var;    # "foo"

这种远距离的行动通常是令人惊讶和危险的。没有迹象表明用户strip将修改其参数。更安全和更明显的做法是将值作为参考传递。

sub strip {
    my $ref = shift;
    $$ref =~ s{^\s+}{};
    $$ref =~ s{\s+$}{};
}

my $var = "   foo   ";
strip(\$var);
print $var;    # "foo"

这既可以节省内存(仅复制引用),也可以让您在子例程中命名参数,并且由于它们必须传递引用,因此可以让调用者知道他们的变量可能被修改。

另一种方法是使用只读别名。这为您提供了不复制变量的内存优化,允许您命名变量,但防止您意外更改它。

有几种方法可以实现这一点,但是Method::Signatures使它很方便。

use Method::Signatures;

func no_copy($string is alias is ro) {
    # $string is an alias to $var
    print "$string\n";

    # But it cannot be altered because $string is read-only.
    # This will throw an error.
    $string .= "bar";
}

my $var = "foo";
no_copy($var);
于 2012-09-21T22:51:49.217 回答
2

不会发生复制$_[0]$str因为它们是同一变量的两个不同名称。

$ perl -E'my $str; sub { say \$str == \$_[0] ?1:0 }->( $str );'
1

是在 中制作副本的作业my ( $str ) = @_;

于 2012-09-21T18:52:08.963 回答