【问题标题】:Why does this Perl variable keep its value为什么这个 Perl 变量保持它的值
【发布时间】:2013-06-25 17:21:05
【问题描述】:

以下两个 Perl 变量声明有什么区别?

my $foo = 'bar' if 0;

my $baz;
$baz = 'qux' if 0;

当它们出现在循环的顶部时,差异是显着的。例如:

use warnings;
use strict;

foreach my $n (0,1){
    my $foo = 'bar' if 0;
    print defined $foo ? "defined\n" : "undefined\n";
    $foo = 'bar';
    print defined $foo ? "defined\n" : "undefined\n";
}

print "==\n";

foreach my $m (0,1){
    my $baz;
    $baz = 'qux' if 0;
    print defined $baz ? "defined\n" : "undefined\n";
    $baz = 'qux';
    print defined $baz ? "defined\n" : "undefined\n";
}

结果

undefined
defined
defined
defined
==
undefined
defined
undefined
defined

似乎if 0 失败了,所以foo 永远不会重新初始化为undef。在这种情况下,它首先是如何声明的?

【问题讨论】:

  • perl -MO=Deparse yourfile 将那些if 0 行打印为'???';,并且重新编译的输出因缺少明确的包名称而死。所以我的结论是应该避免这样的结构,如果 Perl 有一个标准,这将被记录为行使未定义的行为等。但也许有人会提供 perlguts 的解释。
  • @Julian Fondren, '???' 只是意味着代码被优化掉了。 (例如,perl -MO=Deparse -e"1,2,3"perl -MO=Deparse -le"print 'abc' if 0;" 显示问号,即使两个 sn-ps 都是完全合法的。)解析显示 my $x if 0 的问号;如果my $x if 0; 被允许,那将是一个错误,因为my 具有编译时效果,尽管它的运行时效果确实被优化掉了。

标签: perl variables initialization declaration


【解决方案1】:

请参阅 ikegami 的更好答案,可能在上面。

在第一个示例中,由于条件的原因,您从未在循环内定义 $foo,因此当您使用它时,您是在引用然后将值分配给隐式声明的全局变量。然后,第二次通过已经定义外部变量的循环。

在第二个示例中,每次执行块时都会在块内定义 $baz。所以第二次循环是一个新的、尚未定义的局部变量。

【讨论】:

  • 不应该use warningsuse strict 对此有什么要说的吗?他们没有。
  • “包含范围内的隐式声明变量”。呃,也就是说,一个包变量。但他不是,你可以通过给$::foo$::baz 一个值并在defined 通过时打印它们来看到。当在这些循环之外有一个他们可以引用的词法变量时,同样的测量表明两个循环的行为都没有改变。
  • 没有“包含范围内隐式声明的变量”之类的东西。隐式;y 声明的变量是全局的包变量。 $foo$baz 都是词法变量。请参阅我的答案以了解实际发生的情况。
【解决方案2】:

首先,请注意my $foo = 'bar' if 0; 被记录为未定义的行为,这意味着它可以做任何事情,包括崩溃。但无论如何我会解释会发生什么。


my $x 具有三个已记录的效果:

  • 它在编译时声明一个符号。
  • 它会在执行时创建一个新变量。
  • 它在执行时返回新变量。

简而言之,它应该类似于 Java 的 Scalar x = new Scalar();,只是它在表达式中使用时返回变量。

但如果它真的以这种方式工作,以下将创建 100 个变量:

for (1..100) {
   my $x = rand();
   print "$x\n";
}

这意味着对于 my 而言,每次循环迭代都会分配两个或三个内存!一个非常昂贵的前景。相反,Perl 只创建一个变量并在作用域结束时清除它。所以实际上,my $x 实际上做了以下事情:

  • 它在编译时声明一个符号。
  • 它在编译时创建变量[1]
  • 它会在堆栈上放置一个指令,该指令将在退出范围时清除[2]变量。
  • 它在执行时返回新变量。

因此,只创建了一个变量[2]。这比每次进入范围时创建一个 CPU 效率高得多。

现在考虑如果您有条件地执行my 会发生什么,或者根本不执行。通过这样做,您可以防止它放置指令以清除堆栈上的变量,因此变量永远不会丢失其值。显然,这不应该发生,所以这就是为什么 my ... if ...; 是不允许的。


一些利用实现如下:

sub foo {
   my $state if 0;
   $state = 5 if !defined($state);
   print "$state\n";
   ++$state;
}

foo();  # 5
foo();  # 6
foo();  # 7

但是这样做需要忽略禁止它的文档。以上可以安全地使用

{
   my $state = 5;
   sub foo {
      print "$state\n";
      ++$state;
   }
}

use feature qw( state );  # Or: use 5.010;

sub foo {
   state $state = 5;
   print "$state\n";
   ++$state;
}

注意事项:

  1. “变量”有多种含义。我不确定这里哪个定义是准确的,但没关系。

  2. 如果除了 sub 本身之外的任何东西都包含对变量的引用 (REFCNT>1) 或者如果变量包含一个对象,则该指令用一个新的变量替换该变量(在范围退出时)而不是清除现有的变量。这允许以下内容正常工作:

    my @a;
    for (...) {
        my $x = ...;
        push @a, \$x;
    }
    

【讨论】:

  • 我仍然不明白为什么 somemy 行为有效(我们稍后不会得到 Global symbol "$foo" requires explicit package name)但有些无效(指令清除堆栈上的变量)。
  • 正如他所说,词法/全局的区别发生在编译时,不受条件的影响。 clear 指令是一种运行时行为,被if 0 禁用。
  • @ajwood,变量声明发生在编译时(代码是否运行无关紧要),而清除发生在运行时(重要的地方)。 -ikegami
  • 在注释 1 上,它不再准确。如果条件总是假的,或者仅仅是假的,实际上似乎有一种技巧可以做不同的事情。试试perl -wle 'sub foo {my $state = shift if @_; print $state} foo(); foo(5); foo()' 看看我的意思。这修复了旧的做事方式中常见的难以发现的错误。 (我衷心感谢修复它的人!)
  • @btilly,您提供的代码从 Perl 5.8(我最早拥有)到 5.19.5(存在的最新版本)打印相同的内容。我不知道你认为发生了什么变化,但你的代码并不能证明这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-16
  • 2017-05-15
  • 1970-01-01
  • 2020-05-16
相关资源
最近更新 更多