jrw has asked for the wisdom of the Perl Monks concerning the following question:

/usr/bin/perl -we ' use strict; sub fn { my ($str) = @_; print "<@_>\n"; $str =~ s/x//; print "<@_>\n"; } $_ = "x"; s/(x)/fn $1/e; ' <x> Use of uninitialized value $_[0] in join or string at -e line 5. <>
Can someone explain why @_ is modified? I thought my ($str) = @_ made a copy of the elements of @_.

Replies are listed 'Best First'.
Re: Strange modification of @_
by ikegami (Patriarch) on Apr 23, 2009 at 17:52 UTC

    $_[0] is aliased to $1. Clearing $1 will therefore clear $_[0], being the same variable. Don't pass globals to functions if that global is going to be modified in the function.

    Fix:

    s/(x)/fn "$1"/e;

    This causes a new string to be created from the value of $1.

    Update: Note that this isn't usually a problem with Pure Perl subs since @_ is usually only used at the top of the sub.

      Yes, that's it -- don't know why I couldn't see it. However, aarrgghh, I don't see an obvious fix for this that doesn't rely on fn() knowing how it's called or the caller (in the outer substitution) knowing how fn() is going to use its argument list. It seems very unnatural to write s/(x)/fn "$1"/e (with quotes around the argument) and it seems equally unnatural to write the body of fn() any differently (by making a copy of $_[0] before performing the substitution. In general, I certainly don't expect that the argument list to a function will be changed merely by doing something as innocuous as performing substitution on a local string.

      I virtually never use fn("$var") instead of fn($var), but I guess if var is a global, including $1, $2, etc, then the safest thing to do is wrap it in quotes to copy the value.

        it seems equally unnatural to write the body of fn() any differently (by making a copy of $_[0] before performing the substitution.

        What are you talking about? Using one of my ($str) = @_; or my $str = shift; is standard practice, hardly unnatural.

        If the issue is that you want both the original and the transformed string, you might also be interested in the following idiom:

        sub fn { my ($old) = @_; (my $new = $old) =~ s/.../.../; ... }
Re: Strange modification of @_
by ELISHEVA (Prior) on Apr 23, 2009 at 18:26 UTC

    my ($str) = @_ does make a copy. Your complaint about uninitialized values isn't coming from the value of $str but rather from the fact that s/(x)// sets $1 to undefined. In fact s/(x)// doesn't even set $str to undef. It sets it to the empty string.

    Perl subs pass by reference. Since fn $1 passes $1 as a parameter $_[0] and $1 share the same memory address. The error you are seeing is because print "@_" is equivalent to print "$_[0]" which is equivalent to print "$1" and $1 is undefined. I've modified your code sample a bit to make what is going on clearer:

    sub fn { my ($str) = @_; my $rStr = \$str; my $rParam0 = \$_[0]; #print "params=<@_> param0=$rParam0 <$str> copy=$rStr\n"; printf "\\\$1=<%s> \\\$_[0]=<%s> str=<%s> \$1=<%s>\n" , \$1, \$_[0] , (defined($str) ? $str : 'undef') , (defined($1) ? $1 : 'undef'); $str =~ s/x//; printf "\\\$1=<%s> \\\$_[0]=<%s> str=<%s> \$1=<%s>\n" , \$1, \$_[0] , (defined($str) ? $str : 'undef') , (defined($1) ? $1 : 'undef'); } $_ = "x"; s/(x)/fn $1/e;

    outputs

    \$1=<SCALAR(0x818ae08)> \$_[0]=<SCALAR(0x818ae08)> str=<x> $1=<x> \$1=<SCALAR(0x818ae08)> \$_[0]=<SCALAR(0x818ae08)> str=<> $1=<undef>

    Best, beth

Re: Strange modification of @_
by jwkrahn (Abbot) on Apr 23, 2009 at 18:59 UTC

    The use of @_ is a red herring here.   The same problem can be demonstated with this one-liner:

    $ perl -Mwarnings -le' $_ = "x"; s/(x)/ my $str = $1; print "$1"; $str + =~ s<x><>; print "$1"; /e; ' x Use of uninitialized value in string at -e line 1.

    The problem is that the second substitution, even though in a subroutine, clears the $1 variable.