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


Hi,

I was playing with the "print" function recently and I found a something I can't explain.

my $x = 0;
print $x, $x++;  # Print 10??

I was expecting 00 instead of 10. Any idea?

Thanks in advance.

Replies are listed 'Best First'.
Re: print behavior
by ikegami (Patriarch) on Jul 09, 2007 at 18:47 UTC

    Remember that arguments are passed by reference*, so print will see the change in $x caused by ++ (which must be evaluated before print is called).

    In detail,

    print $x, $x++;

    is roughly equivalent to

    { local @_; alias $_[0] = $x; alias $_[1] = $x++; &print; }

    except the order of the alias statements isn't defined.

    Given $x=0, @_ is ($x, $anon) which evaluates to (1, 0).

    In short, avoid modifying a variable in a parameter list expression when the variable appears elsewhere in that parameter list.

    * — "by reference" is different than "as a reference".

      With due respect to ikegami and all other monks, I strongly disagree with ikegami for the following reasons.

      1. How will you explain the peculiar behavior I have explained in my post Re: print behavior when we have more than one $x++ in same print statement ? ( the table is appended below for reference.

        Print orderO/P in Perl
        $x0
        $x,$x++10
        $x,$x,$x++110
        $x,$x++,$x++201
        $x,$x++,$x++,$x++3012
        $x,$x++,$x++,$x++,$x++40123
        $x,$x++,$x++,$x++,$x++,$x++501234

      2. How we have the similar behaviour with "C" where printf("%d%d",x,x++) which will also print 10?
      3. you said "print will see the change in $x caused by ++ (which must be evaluated before print is called)." Then why
        my $x =0; print $x++; #prints 0 print $x++; #prints 1
        works properly? Why in this case $x++ is NOT incrementing $x before print is called?
      4. Given $x=0, @_ is ($x, $anon) which evaluates to (1, 0). --- Can you explain how the @_ is evaluated to ( 1, 0 ) ?
      5. arguments are passed by reference --- if its passed by reference, how will you explain this behaviour for all function calls? including user-defined-functions? (see comments in my post Re: print behavior)

      If you keep the explanation I posted at Re: print behavior in mind, there is NO harm in modifying a variable in a parameter list expression when the variable appears elsewhere in that parameter list. Isn't it a nice obfuscation.

        That's a lot of questions! Here goes...

        Answer to Question 1

        I'll do two examples, $x,$x,$x++:

        { local @_; alias $_[0] = $x; alias $_[1] = $x; alias $_[2] = $x++; &print; # @_ = ($x, $x, $anon) = (1, 1, 0) }

        $x,$x++,$x++:

        { local @_; alias $_[0] = $x; alias $_[1] = $x++; alias $_[2] = $x++; &print; # @_ = ($x, $anon1, $anon2) = (2, 1, 0) }

        $x,$x++,$x++, on a system that does the alias statments in a different order:

        { local @_; alias $_[2] = $x++; alias $_[1] = $x++; alias $_[0] = $x; &print; # @_ = ($x, $anon1, $anon2) = (2, 0, 1) }

        Answer to Question 2

        Your compiler probably does something like:

        load x inc push load x push call printf

        C doesn't guarantee the order either.

        Answer to Question 3

        The return value of $x++ is not $x. It's a new variable that contains the old value of $x. $x is incremented before the print call, but you are aren't printing $x. Check perlop.

        Answer to Question 4

        # $x | anon returned | $_[0] | $_[1] # | by $x++ | | # ------+-----------------+---------+--------- my $x = 0; # 0 | -- | -- | -- { # 0 | -- | -- | -- local @_; # 0 | -- | undef | undef alias $_[0] = $x; # 0 | -- | 0 | undef alias $_[1] = $x++; # 1 | 0 | 1 | 0 &print; }

        or in even more detail

        # $x | anon returned | $_[0] | $_[1] # | by $x++ | | # ------+-----------------+---------+--------- my $x = 0; # 0 | -- | -- | -- { # 0 | -- | -- | -- local @_; # 0 | -- | undef | undef alias $_[0] = $x; # 0 | -- | 0 | undef my $anon = $x++; # 1 | 0 | 1 | undef alias $_[1] = $anon; # 1 | 0 | 1 | 0 &print; }

        Answer to Question 5

        Guess what the following prints:

        perl -le"$x=3; sub { $_[1]++; $_[2]++; print @_ }->($x+0, ++$x, $x++, +$x+0, $x);"

        On my system, it prints 36556.

        my $x = 3; # $anon0 | $x | $anon2 | $anon3 | $x { # $_[0] | $_[1] | $_[2] | $_[3] | $_[4] local @_; # --------+-------+--------+--------+------- # $x+0 my $anon0 = $x+0; alias $_[0] = $anon0; # 3 | -- | -- | -- | -- # ++$x $x=$x+1; # 3 | -- | -- | -- | -- alias $_[1] = $x; # 3 | 4 | -- | -- | -- # $x++ my $anon2 = $x; alias $_[2] = $anon2; # 3 | 4 | 4 | -- | -- $x=$x+1; # 3 | 5 | 4 | -- | -- # $x+0 my $anon3 = $x+0; alias $_[3] = $anon3; # 3 | 5 | 4 | 5 | -- # $x alias $_[4] = $x; # 3 | 5 | 4 | 5 | 5 &anon_sub; # $_[1]++; # 3 | 6 | 4 | 5 | 6 # $_[2]++; # 3 | 6 | 5 | 5 | 6 # print @_; }

        Notice how $x and $x+0 are different.
        Notice how $_[1]++ affects more than one argument.
        Notice how $_[1]++ affects $x and ++$x.
        Notice how $_[1]++ doesn't affect $x+0 and $x++.

Re: print behavior
by FunkyMonk (Bishop) on Jul 09, 2007 at 17:50 UTC
    While it doesn't explain your findings, the short answer is don't try to use a variable twice in a single statement if there is code in that statement that modifies the value held in that variable.
Re: print behavior
by toolic (Bishop) on Jul 09, 2007 at 17:54 UTC
    The interpreter is behaving as if it is evaluating $x++ before it prints anything out. The initial value of $x is 0. So, when $x++ is evaluated, $x then becomes 1.

    Try this:

    print "x=" . $x . "\n"; print "x++=" . $x++ . "\n";
    You should get this output, which is more like what you expect.
    x=0 x++=0
Re: print behavior
by atemon (Chaplain) on Jul 10, 2007 at 07:32 UTC

    Hi Monks

    This behaviour is there with C/C++ "printf" also. We need to know how the "print" statement (see coments below ) is handling variables & its data. Our code in question is

    my $x = 0; print $x, $x++; # Print 10??

    All the arguments passed to the "print" is kept in a stack! print will push arguments from right-to-left into the stack. Inthe above case value of $x goes into stack (corresponding to $x++).

    After the first "push", $x is incremented and on second "push" new value of $x is pushed. Then it start poping to print. For sure it will pop 1 first and then 0. So it will print 10. A program to demo this can be

    #!/usr/bin/perl use strict; my @PRINT_STACK; my $x = 0; push @PRINT_STACK, $x++; print "PRINT_STACK => ( ",@PRINT_STACK, " )\n"; push @PRINT_STACK, $x; print "PRINT_STACK =>( ",join(", ",@PRINT_STACK)," )\n"; print pop @PRINT_STACK; print pop @PRINT_STACK; exit();

    Again funny facts :)

    my $x = 0; print $x,$x, $x++; # Print 110??

    This will print 110 for sure, because the value of $x is NOT changed after first push.

    Then what would be

    my $x = 0; print $x,$x++, $x++; # Print 210??

    In case of "C" it will print 210 as expected. but the output you will get in perl is 201. If I make a table with more $x++ in print, it will be as follows (assuming that initial value of $x is 0 ).

    Print orderO/P in CO/P in Perl
    $x00
    $x,$x++1010
    $x,$x,$x++110110
    $x,$x++,$x++210201
    $x,$x++,$x++,$x++32103012
    $x,$x++,$x++,$x++,$x++4321040123
    $x,$x++,$x++,$x++,$x++,$x++543210501234

    "C" is working as I explained above. Why Perl is behaving differently when we have "$x,$x++,$x++" ? Answer is again stacks.

    when there are more than one expressions in same statement, perl uses another stack for evaluating them. ie, it wll push 0 to stack first. Then increments $x to 1. Then again it pushes 1 to stack and increments it to 2. Now all operations are over. Now Perl with pop contents of the operation stack and push to print stack. Then push the last $x. So it will print "201" for

    print $x,$x++,$x++

    Following program will demonstrate the entire thing :)

    #!/usr/bin/perl use strict; my @PRINT_STACK; my @OP_STACK; my $x = 0; my $temp; #print $x,$x++,$x++; push @OP_STACK, $x++; # push the l +ast $x++ to the operator stack (pushes 0) print "OP_STACK => ( ",@OP_STACK, " )\n"; push @OP_STACK, $x++; # push the n +ext $x++ operator to the operator stack (pushes 1) print "OP_STACK => ( ",join(", ",@OP_STACK)," )\n"; # All Operations are over $temp = pop @OP_STACK; #pop The ope +rator stack and push it to print stack (pops 1) push @PRINT_STACK, $temp; # pushes 1 print "PRINT_STACK => ( ",join(", ",@PRINT_STACK)," )\n"; $temp = pop @OP_STACK; #pop The ope +rator stack and push it to print stack (pops 0) push @PRINT_STACK, $temp; #pushes 0 print "PRINT_STACK => ( ",join(", ",@PRINT_STACK)," )\n"; push @PRINT_STACK, $x; # push the f +irst $x to the operator stack (pushes 2) print "PRINT_STACK => ( ",join(", ",@PRINT_STACK)," )\n"; print pop @PRINT_STACK; print pop @PRINT_STACK; print pop @PRINT_STACK; exit();

    Isn't this a nice hack for obfuscation ?

    Comments

    • This behaviour goes with all function calls, NOT only print. It is the implementation of a function call. Please try the code
      $x=0; &myprint($x, $x++); sub myprint{ print join ",",@_; }
      Try puting more $x++ for the function call &myprint

    Cheers!

    --VC

    UPDATE : I am not sure about all C compilers. I got above behavior with "gcc on FC6 & Turbo C on Windows XP". Also for Perl I tested with "Active Perl On Windows XP & Perl on FC6". I was NOT trying to say Perl & C will behave alike, but I was trying to say that this IS there with most of the implementation of C (gcc on FC6 and turbo C on Windows XP) & Perl (Active Perl On Win XP and perl on FC6). SORRY if misleading.

      You're making the mistake of assuming Perl and C will behave the same on all systems. Statements like "This will print 110 for sure" and "In case of 'C' it will print 210 as expected." are wrong. The order in which the args are evaluated is not defined in either language. That's why it's harmful to modify a variable in a parameter list expression when the variable appears elsewhere in that parameter list.

        I am not sure about all C compilers. I got above behavior with "gcc on FC6 & Turbo C on Windows XP". Also for Perl I tested with "Active Perl On Windows XP & Perl on FC6". I was NOT trying to say Perl & C will behave alike, but I was trying to say that this IS there with most of the implementation of C (gcc on FC6 and turbo C on Windows XP) & Perl (Active Perl On Win XP and perl on FC6). SORRY if misleading.


        Obfuscation is NOT the only measure of expertise.

      Why do you update your post instead of answering to ikegami's reply? This makes it rather difficult to follow the discussion.

      Also, a note about the undefined order of execution. The fact that some compilers seem to behave in a certain manner does not mean that you can rely on it. It means that they can change their mind whenever they want, without notice, and it's simply plain wrong to rely on it.

      This said, it can probably be useful only in some golfing competition, where you can be sure that a certain version of perl (with the lowercase "p") on a certain architecture will be used, and you can thus take advantage of any implementation decision, feature or bug this perl exhibits. Usage in obfuscation is weaker, IMHO, because it could behave differently across different versions or architectures.

      Flavio
      perl -ple'$_=reverse' <<<ti.xittelop@oivalf

      Don't fool yourself.
Re: print behavior
by polettix (Vicar) on Jul 10, 2007 at 13:33 UTC
Re: print behavior
by bart (Canon) on Jul 11, 2007 at 12:20 UTC
    Try this as a variation:
    my $x = 0; print "$x", $x++;
    and figure out why this does print "00".

    The reason for the difference is that on your case the first parameter is an alias to the variable $x, while in my case it's an expression with a stringified copy of the value of $x at the time the parameter value was evaluated.

    An alias can still change its value later – when the value of $x changes. A copy can not.

Re: print behavior
by Moron (Curate) on Jul 12, 2007 at 08:36 UTC
    Many different replies about something being wrong and yet if we break down into the ordered steps one should expect Perl or even C to take:

    1) evaluate a list of two arguments 0) $x = 0 and 1) $x++ = 0.

    2) increment $x so that the list of arguments is now 0: $x=1 and 1) $x++ = 0 (stays unchanged - that's what postincrement is for!)

    3) Call the function with the two arguments whose values we just established should be 1 and 0 respectively.

    Then we see that the results are the only results we should expect to happen!

    __________________________________________________________________________________

    ^M Free your mind!