为什么我可以在声明没有错误之前获取子程序地址?

时间:2017-07-04 12:22:32

标签: perl subroutine autovivification

我有下一个程序:

use warnings;
use strict;

BEGIN {
    print \&mysub;
}


sub mysub {};

print \&mysub;

其输出:

CODE(0x118e890)CODE(0x118e890)

BEGIN块在编译时处理。那时编译器还没有看到sub mysub的定义。但是程序仍会打印正确的子程序地址,它在定义时会有。

为什么我这里没有错误?这是某种自动化吗?

2 个答案:

答案 0 :(得分:4)

是的,这是一种自动化的形式。当需要对sub的引用并且sub不存在时,会创建存根。

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

sub test {
   say  defined(&mysub) ? "defined (".\&mysub.")"
      : exists(&mysub)  ? "exists (".\&mysub.")"
      :                   "doesn't exist";
}

test();
my $ref = \&mysub;
test();
eval("sub mysub { }  1") or die($@);
test();

输出:

doesn't exist
exists (CODE(0xab8cd8))
defined (CODE(0xab8cd8))

答案 1 :(得分:2)

非常有趣的问题。我写这个作为答案而不是评论,因为它会很长,但仍有一些我不太确定。

我相信你的直觉是正确的,它是一种自动化的形式。

Devel::Peek可以更好地了解正在发生的事情。

我改变了你的代码:

use warnings;
use strict;
use Devel::Peek;

$|++;

BEGIN {
  Dump( \&mysub );
  print \&mysub;
};

sub mysub {};

Dump( \&mysub );
print \&mysub;

我添加了$|++,因此缓冲不会导致混淆,并添加了对Devel::Peek::Dump的调用以查看引用\&mysub。这是我系统上的输出:

SV = IV(0x2628628) at 0x2628638
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x26286e0
  SV = PVCV(0x2640750) at 0x26286e0
    REFCNT = 2
    FLAGS = (DYNFILE)
    COMP_STASH = 0x25ffdb0  "main"
    ROOT = 0x0
    GVGV::GV = 0x26287a0    "main" :: "mysub"
    FILE = "/tmp/autov.pl"
    DEPTH = 0
    FLAGS = 0x1000
    OUTSIDE_SEQ = 0
    PADLIST = 0x0
    OUTSIDE = 0x0 (null)
CODE(0x26286e0)SV = IV(0x25fff20) at 0x25fff30
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x26286e0
  SV = PVCV(0x2640750) at 0x26286e0
    REFCNT = 2
    FLAGS = (DYNFILE)
    COMP_STASH = 0x25ffdb0  "main"
    START = 0x262ea50 ===> 1
    ROOT = 0x262ea10
    GVGV::GV = 0x26287a0    "main" :: "mysub"
    FILE = "/tmp/autov.pl"
    DEPTH = 0
    FLAGS = 0x1000
    OUTSIDE_SEQ = 371
    PADLIST = 0x2648620
    PADNAME = 0x2630180(0x2667f70) PAD = 0x2628770(0x262f020)
    OUTSIDE = 0x2600140 (MAIN)
CODE(0x26286e0)

请注意Dump的输出在两次调用之间的变化情况。 第一次调用Dump时,我们只能引用一个空标量。 第二时间,在子例程的实际定义之后,您可以看到与子例程相关的详细信息已经充实:即PADLIST(现在不为空),PADNAMESTART(我不是Perl guts的专家,但我认为这个是实际的"指针"到子程序)。

我希望这会有所帮助。如果你能深入挖掘这个问题,我有兴趣知道你会发现什么。