9
$a = 10; 
$b = (++$a) + (++$a) + (++$a);
print $b;

I am getting the answer 37. Can anybody explain how this operation is proceeding and how the result is getting 37.

As per my logic it should be 36:

(++$a) + (++$a) + (++$a)
   11  +    12  +    13  = 36

But I am getting the answer 37

Jim Davis
  • 5,241
  • 1
  • 26
  • 22
Abhishek Saha
  • 171
  • 1
  • 1
  • 6
  • 1
    See [perlop](https://metacpan.org/pod/distribution/perl/pod/perlop.pod#Auto-increment-and-Auto-decrement) – Chris Charley Apr 21 '17 at 15:48
  • 3
    The relevant part from the link in @ChrisCharley comment is _Note that just as in C, Perl doesn't define when the variable is incremented or decremented. You just know it will be done sometime before or after the value is returned. This also means that modifying a variable twice in the same statement will lead to undefined behavior_. Anyone going to write an answer? – simbabque Apr 21 '17 at 16:02
  • 1
    Seems like `$a` is preincremented twice first, so you get `12 + 12 + 13`. You can verify this by running the script under [`B::Concise`](https://metacpan.org/pod/B::Concise) – Håkon Hægland Apr 21 '17 at 16:28
  • 5
    [Similar question about ++ in C](http://stackoverflow.com/q/949433/4990392). Just to be sure: you ask out of curiosity, and not because you tried to use such a construct right? – Dada Apr 21 '17 at 16:40
  • 2
    The answers are all trying to explain what happens behind the scenes in this particular instance. Instead, they ought to just say "don't do that". Any effort you put into understanding why a certain answer is produced will have been wasted if something simple changes from one version to another. See also the C FAQ: [3.1](http://c-faq.com/expr/evalorder1.html), [3.2](http://c-faq.com/expr/evalorder2.html), [3.3](http://c-faq.com/expr/ieqiplusplus.html), and [3.9](http://c-faq.com/expr/evalorder4.html) [etc](http://c-faq.com/expr/index.html). – Sinan Ünür Apr 21 '17 at 21:27

4 Answers4

9

Perl's is executing this as

( ( $a = $a + 1 ) + ( $a = $a + 1 ) ) + ( $a = $a + 1 )

You have even put the ++$a in parentheses so to say that they should happen first, before the additions, although they are of higher priority anyway

This is centred around the fact that the assignment operator = returns its first operand, which allows operations like

(my $x = $y) =~ tr/A-Z/a-z/

If the result of the assignment were simply the value copied from $y to $x then the tr/// would cause a Can't modify a constant item or the equivalent, and it would have no effect on what was stored in either variable

Here is the variable $a, and the execution is as follows

  • Execute the first increment, returning $a
    $a is now 11

  • Execute the second increment, returning $a again
    $a is now 12

  • Execute the first addition, which adds what was returned by the two increments—both $a
    $a is 12, so $a + $a is 24

  • Execute the third increment, returning $a again
    $a is now 13

  • Execute the second addition, which adds the what was returned by the first addition (24) and the third increment ($a)
    $a is 13, so 24 + $a is 37

Note that this should not be relied on. It is not documented anywhere except to say that it us undefined, and the behaviour could change with any release of Perl

Borodin
  • 126,100
  • 9
  • 70
  • 144
6

As a complement to mob and Borodin's answer, you can see what's happening clearly if you think about how the operations are interacting with the stack and recognize that preinc returns the variable, not its value.

op | a's value | stack
$a | 10        | $a
++ | 11        | $a
$a | 11        | $a $a
++ | 12        | $a $a
+  | 12        | 24
$a | 12        | 24 $a
++ | 13        | 24 $a
+  | 13        | 37
ysth
  • 96,171
  • 6
  • 121
  • 214
5

As it has been noted in comments, changing a variable multiple times within a single statement leads to undefined behavior, as explained in perlop.

So the exact behavior is not specified and may vary between versions and implementations.

As to how it works out, here is one way to see it. Since + is a binary operator, at each operation its left-hand-side operand does get involved when ++ is executed on the other. So at each position $a gets ++ed, and picks up another increment as a LHS operand.

That means that the LHS $a gets incremented additionally (to its ++) once in each + operation. The + operations after the first one must accumulate these, one extra for each extra term. With three terms here that's another +3, once. So there are altogether 7 increments.

Yet another (fourth) term incurs an extra +4, etc

perl -wE'$x=10; $y = ++$x + ++$x + ++$x + ++$x; say $y'  # 4*10 + 2+2+3+4

This is interesting to tweak by changing ++$x to $x++ -- the effect depends on position.

Increments in steps

  • first $a gets incremented (to 11)

  • in the first addition, as the second $a is incremented (to 11) the first one gets a bump as well being an operand (to 12)

  • in the second addition, the second $a gets incremented (to 12) as an operand

  • as the second addition comes up, the third $a is updated and thus picks up increments from both additions, plus its increment (to 13)

The enumeration of $a above refers to their presence at multiple places in the statement.

zdim
  • 64,580
  • 5
  • 52
  • 81
4

As @Håkon Hægland pointed out, running this code under B::Concise, which outputs the opcodes that the Perl script generates, is illuminating. Here's are two slightly different examples than the one you provided:

$ perl -E 'say $b=$a + ((++$a)+(++$a))'
6
$ perl -E 'say $b=($a+(++$a)) + (++$a)'
4

So what's going on here? Let's look at the opcodes:

$ perl -MO=Concise -E 'say $b=$a+((++$a)+(++$a))'
e  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 47 -e:1) v:%,{,469764096 ->3
d     <@> say vK ->e
3        <0> pushmark s ->4
c        <2> sassign sKS/2 ->d
a           <2> add[t6] sK/2 ->b
-              <1> ex-rv2sv sK/1 ->5
4                 <#> gvsv[*a] s ->5
9              <2> add[t5] sKP/2 ->a
6                 <1> preinc sKP/1 ->7
-                    <1> ex-rv2sv sKRM/1 ->6
5                       <#> gvsv[*a] s ->6
8                 <1> preinc sKP/1 ->9
-                    <1> ex-rv2sv sKRM/1 ->8
7                       <#> gvsv[*a] s ->8
-           <1> ex-rv2sv sKRM*/1 ->c
b              <#> gvsv[*b] s ->c
-e syntax OK

There are no conditionals in this program. The left most column indicates the order of operations in this program. Whereever you see the ex-rv2sv token, that is where Perl is reading the value of an expression like a global scalar variable.

The preinc operations occur at labels 6 and 8. The add operations occur at labels 9 and a. This tells us that both increments occurred before Perl performed the additions, and so the final expression would be something like 2 + (2 + 2) = 6.

In the other example, the opcodes look like

$ perl -MO=Concise -E 'say $b=($a+(++$a)) + (++$a)'
e  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 47 -e:1) v:%,{,469764096 ->3
d     <@> say vK ->e
3        <0> pushmark s ->4
c        <2> sassign sKS/2 ->d
a           <2> add[t6] sK/2 ->b
7              <2> add[t4] sKP/2 ->8
-                 <1> ex-rv2sv sK/1 ->5
4                    <#> gvsv[*a] s ->5
6                 <1> preinc sKP/1 ->7
-                    <1> ex-rv2sv sKRM/1 ->6
5                       <#> gvsv[*a] s ->6
9              <1> preinc sKP/1 ->a
-                 <1> ex-rv2sv sKRM/1 ->9
8                    <#> gvsv[*a] s ->9
-           <1> ex-rv2sv sKRM*/1 ->c
b              <#> gvsv[*b] s ->c
-e syntax OK

Now the preinc operations still occur at 6 and 9, but there is an add operation at label 7, after $a has only be incremented one time. This makes the values used in the final expression (1 + 1) + 2 = 4.

So in your example:

$ perl -MO=Concise -E '$a=10;$b=(++$a)+(++$a)+(++$a);say $b'
l  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 47 -e:1) v:%,{,469764096 ->3
5     <2> sassign vKS/2 ->6
3        <$> const[IV 10] s ->4
-        <1> ex-rv2sv sKRM*/1 ->5
4           <#> gvsv[*a] s ->5
6     <;> nextstate(main 47 -e:1) v:%,{,469764096 ->7
g     <2> sassign vKS/2 ->h
e        <2> add[t7] sK/2 ->f
b           <2> add[t5] sK/2 ->c
8              <1> preinc sKP/1 ->9
-                 <1> ex-rv2sv sKRM/1 ->8
7                    <#> gvsv[*a] s ->8
a              <1> preinc sKP/1 ->b
-                 <1> ex-rv2sv sKRM/1 ->a
9                    <#> gvsv[*a] s ->a
d           <1> preinc sKP/1 ->e
-              <1> ex-rv2sv sKRM/1 ->d
c                 <#> gvsv[*a] s ->d
-        <1> ex-rv2sv sKRM*/1 ->g
f           <#> gvsv[*b] s ->g
h     <;> nextstate(main 47 -e:1) v:%,{,469764096 ->i
k     <@> say vK ->l
i        <0> pushmark s ->j
-        <1> ex-rv2sv sK/1 ->k
j           <#> gvsv[*b] s ->k
-e syntax OK

We see preinc occurring at labels 8, a, and d. The add operations occur at b and e. That is, $a is incremented twice, then two $a's are added together. Then $a is incremented again. Then $a is added to the result. So the output is (12 + 12) + 13 = 37.

mob
  • 117,087
  • 18
  • 149
  • 283
  • 1
    Do you know which book covers reading the optree properly? I understand what's going on in general, but it's mostly groking, not understanding. – simbabque Apr 21 '17 at 18:41
  • 2
    Not really. I'm also just reading the `B::Concise` output and making an educated guess about what it all means. I also know ikegami is never too far away, and he'll let me know if I screwed up the explanation :-) – mob Apr 21 '17 at 19:18
  • 1
    @simbabque: I found the [*OP TREES* section of `perlinterp`](http://perldoc.perl.org/perlinterp.html#OP-TREES) to be a good entry point. After that, there is all of the associated [`perldoc` stuff on *Internals and C Language Interface*](http://perldoc.perl.org/perl.html#Internals-and-C-Language-Interface). It's probably best to start by familiarising yourself with what's in each document than working through trying to absorb it all. Writing XS code is good memory training. – Borodin Apr 21 '17 at 19:37
  • 1
    often -MO=Concise,-exec is more readable; it ditches the optimized away ops and the tree and just shows the ops in order of execution – ysth Apr 21 '17 at 22:09