简体   繁体   English

需要帮助来了解Perl 5如何解析引用相同变量的复合赋值语句

[英]Need help understanding how Perl 5 is parsing a compound assignment statement referencing same variable

I need to understand how this simple expression is being evaluated, since the result is not as I expected. 我需要了解如何评估这个简单表达式,因为结果与我预期的不同。

I'm quite new to Perl, but thought I had enough understanding to explain the outcome of this seemingly straightforward snippet. 我对Perl还是很陌生,但认为我有足够的理解力来解释这个看似简单的代码片段的结果。 Clearly I'm missing something. 显然我缺少了一些东西。 I've used Deparse to see how Perl is processing the expression, and Deparse does not change the parentheses I already had in place. 我已经使用Deparse来查看Perl如何处理该表达式,并且Deparse不会更改已经存在的括号。

$i = 12;
$i = (($i /= 2) + ($i = 100));
print $i;

According to my understanding, the result should be 106, assuming the expression is evaluated in the order indicated by parentheses, and in the manner it seems it should be. 根据我的理解,假设表达式是按括号指示的顺序以及看起来应该采用的方式求值的,则结果应为106。 I would think: $i is first divided by 2, thereby assigning 6 to $i and resulting in a value of 6. Then 100 is assigned to $i, and 100 is the result of that second expression. 我想:首先将$ i除以2,然后将$ 6分配给6,并得出值6。然后将$ 100分配给$ i,而第二个表达式的结果就是100。 6 + 100 = 106, which I would think would finally be assigned to $i. 6 + 100 = 106,我认为它将最终分配给$ i。 Instead, it prints "200" . 而是打印“ 200”。

In PHP, the same code indeed yields "106", leading me to believe that this has to do with some part of the expression being interpreted as a list, or something equally Perl-wonderful. 在PHP中,相同的代码确实会产生“ 106”,这使我相信,这与将表达式的某些部分解释为列表或Perl奇妙的东西有关。 Can't wait to find out what I got wrong. 等不及要找出我做错了什么。

By far and large, most languages don't define what happens when you both read and write the same value variable in a single expression. 总体而言,大多数语言都没有定义当您在单个表达式中读取和写入相同的值变量时会发生什么。 Perl is no exception. Perl也不例外。 The expression you posted does not have a defined result. 您发布的表达式没有定义的结果。

This is documented in perlop: 这记录在perlop中:

modifying a variable twice in the same statement will lead to undefined behavior. 在同一条语句中两次修改变量将导致未定义的行为。 Avoid statements like: 避免使用以下语句:

  $i = $i ++; print ++ $i + $i ++; 

What's happening is that $i /= 2 and $i = 100 both return $i —not the value of $i , but $i itself— so you eventually do $i + $i instead of 6 + 100 . 发生了什么事是$i /= 2$i = 100都返回$i -not价值$i ,但$i itself-所以你最终$i + $i ,而不是6 + 100 You can't count on this behaviour. 您不能指望这种行为。 Furthermore, Perl happened to evaluate the addition's left operand before its right one —something else you can't count on— so $i is 100 come time to perform the addition. 而且,Perl碰巧先评估了加法的左操作数,然后才算出它的右操作数(这是您不能指望的其他事情),因此$i是执行加法的100时间。

If someone wants to fool around, here's a recreation of what currently happens when perl evaluates the OP's code: 如果有人想鬼混,这是当perl评估OP的代码时当前发生的事情的再现:

use strict;
use warnings;
use feature qw( say );
use experimental qw( refaliasing declared_refs );

my $i = 12;

my @ST;  # Stack
{                                      \$ST[@ST] = \( $i                    ); }
{                                      \$ST[@ST] = \( 2                     ); }
{ \my ($lhs, $rhs) = \splice(@ST, -2); \$ST[@ST] = \( $lhs /= $rhs          ); }
{                                      \$ST[@ST] = \( 100                   ); }
{                                      \$ST[@ST] = \( $i                    ); }
{ \my ($rhs, $lhs) = \splice(@ST, -2); \$ST[@ST] = \( $lhs = $rhs           ); }
{ \my ($lhs, $rhs) = \splice(@ST, -2); \$ST[@ST] = \( my $sum = $lhs + $rhs ); }
{                                      \$ST[@ST] = \( $i                    ); }
{ \my ($rhs, $lhs) = \splice(@ST, -2); \$ST[@ST] = \( $lhs = $rhs           ); }

say $i;  # 200

If you use Devel::Peek's Dump , you'll notice that most of the variables above have the same address. 如果使用Devel :: Peek的Dump ,您会注意到上面的大多数变量都具有相同的地址。 They're what we call "aliases" in Perl jargon. 它们就是我们在Perl行话中所说的“别名”。

The following uses references instead (though no actual reference is created in reality): 下面使用引用代替(尽管实际上没有创建任何实际引用):

use strict;
use warnings;
use feature qw( say );

my $i = 12;

my @ST;  # Stack
{                                         push @ST, \( $i                          ); }
{                                         push @ST, \( 2                           ); }
{ my ($lhs_p, $rhs_p) = splice(@ST, -2);  push @ST, \( $$lhs_p /= $$rhs_p          ); }
{                                         push @ST, \( 100                         ); }
{                                         push @ST, \( $i                          ); }
{ my ($rhs_p, $lhs_p) = splice(@ST, -2);  push @ST, \( $$lhs_p = $$rhs_p           ); }
{ my ($lhs_p, $rhs_p) = splice(@ST, -2);  push @ST, \( my $sum = $$lhs_p + $$rhs_p ); }
{                                         push @ST, \( $i                          ); }
{ my ($rhs_p, $lhs_p) = splice(@ST, -2);  push @ST, \( $$lhs_p = $$rhs_p           ); }

say $i;  # 200

The perl-wonderful thing is that arguments (whether lvalues or rvalues) are always passed to perl operators as references to the actual variables, not as copies of their values. 令人惊奇的事情是,参数(无论是左值还是右值)始终作为对实际变量的引用而不是其值的副本传递给perl运算符。 This is different from most other languages, and is keeping with the fact that perl is a pass-by-reference language (like Fortran). 这与大多数其他语言不同,并且与perl是传递引用语言(例如Fortran)的事实保持一致。

Your example is an very unfortunate red-herring , since it assumes that the operands of + are evaluated left-to-right, which (while absolutely true for the only usable perl5 implementation) is afaik not guaranteed by any docs. 您的示例是一个非常不幸的红鲱鱼 ,因为它假定+的操作数是从左到右求值的(尽管对于唯一可用的perl5实现而言这是绝对正确的),但任何文档都无法保证。

Let's try it with a comma operator, which really is [1] guaranteed to evaluate its arguments left-to-right: 让我们用comma运算符来尝试一下,它确实 [1]保证从左到右评估其参数:

perl -le 'print @y = ($x = 1, $x = 2, $x = 3)'

That should print 123 right? 那应该打印123对吗?

No, because perl will first evaluate all the assignments from left to right, each of which returning $x , not a copy of it, and then will proceed 3 times to "resolve" it by derefencing it, getting each time in the last value that was stored in it. 否,因为perl首先会评估从左到右的所有分配,每个分配都返回$x而不是其副本, 然后将进行3次以通过取消引用来“解析”它,每次都获取最后一个值存储在其中。 Thence 333 . 从此333

[1]: from perlop(1) : "Comma operator ... 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". [1]:来自perlop(1) :“逗号运算符...在列表上下文中,它只是列表参数分隔符,并将其两个参数都插入列表。这些参数也从左到右求值”。

[the extra assignment above in order to avoid a discussion about how and why in perl an argument list is actually a list, built with the comma-operator, not something special as in C] [上面的额外分配是为了避免讨论在perl如何以及为什么在参数列表中实际上是一个由逗号运算符构建的列表,而不是C语言中的特殊内容]

perldoc perlop: perldoc perlop:

Note that just as in C, Perl doesn't define when the variable is incremented or decremented. 请注意,就像在C中一样,Perl并没有定义变量何时递增或递减。 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. 这也意味着在同一条语句中两次修改变量将导致未定义的行为。 Avoid statements like: 避免使用以下语句:

  $i = $i ++; print ++ $i + $i ++; 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM