compilation order and post prefix opertors

2019-04-26 18:52发布

问题:

I was wondering why the following outputs 7 7 6 7 instead of 5 6 6 7

my $a = 5;
printf("%d %d %d %d",$a,++$a , $a++ , $a);

I'm pretty sure it has something to do with the order of parameters compilation

Thanks,

回答1:

Before I start, let me point out that one should generally avoid situations where one you both sets and reads a variable within an expression.


First, let's look at operand evaluation order. This isn't defined for many operators, but it is defined for the list operator. It's documented to evaluate its operands in left-to-right order[1]. That means that printf's arguments are evaluated in the following order:

  1. "%d %d %d %d"
  2. $a
  3. ++$a
  4. $a++
  5. $a

The key lies in knowing that $a doesn't place a copy of the value of $a on the stack. It places the scalar itself (a SV*, in C terms). In Perl jargon, we say the stack element is aliased to $a[2]. In computing theory, you'd say the arguments are passed by reference.

And the same goes for ++$a, but $a++ necessarily places a copy of $a on the stack.

This means we can view the above printf call as equivalent to

use Data::Alias qw( alias );

{
    local @_;
    alias $_[0] = "%d %d %d %d";
    alias $_[1] = $a;    # Places $a on the stack.
    alias $_[2] = ++$a;  # Adds one to $a and places $a on the stack.
    alias $_[3] = $a++;  # Places a copy of $a on the stack and adds one to $a.
    alias $_[4] = $a;    # Places $a on the stack.
    &CORE::printf;
 }

By the time $a++ is called, $a contains 6.

By the time printf is called, $a contains 7.


The workaround is to make copies of the values.

$ perl -le'$a = 5; my @b = ($a, ++$a, $a++, $a); print "@b";'
7 7 6 7

$ perl -le'$a = 5; my @b = (0+$a, 0+(++$a), $a++, $a); print "@b";'
5 6 6 7

  1. From perlop, "In list context, it's just the list argument separator, and inserts both its arguments into the list. These arguments are also evaluated from left to right."

  2. From perlsyn, "Any arguments passed in show up in the array @_. Therefore, if you called a function with two arguments, those would be stored in $_[0] and $_[1]. The array @_ is a local array, but its elements are aliases for the actual scalar parameters."