我在Perl 5.10.1上运行了以下脚本:
#!/usr/bin/perl
use strict;
use warnings;
foreach( my $x =0 ; $x < 1; $x++) { # Line 5
print_line(); # Line 6
}
sub print_line {
print "Function call from line: " . [caller(0)]->[2] . "\n";
}
尽管调用了来自第6行的子程序,但脚本输出了C语句开头的行号for语句:
Function call from line: 5
如果我将一个随机语句放入C-style for循环中的空行之一,caller
返回正确的行号,真是奇怪:
#!/usr/bin/perl
use strict;
use warnings;
foreach( my $x =0 ; $x < 1; $x++) {
my $x = 3;
print_line(); # Line 7
}
sub print_line {
print "Function call from line: " . [caller(0)]->[2] . "\n";
}
上面的脚本正确输出:
Function call from line: 7
这是某种错误还是我可以采取哪些措施让caller
准确报告行号?
答案 0 :(得分:7)
我认为这可能是一个错误,因为如果你替换
,同样的行为就不会发生foreach (my $x = 0 ; $x < 1 ; $x++) {
与
foreach my $x (0 .. 0) {
我不明白确切地发生了什么,但通过比较两个不同版本的选项,我认为nextstate
op正在进行不正确的优化出。我的版本有
<;> nextstate(main 4 lineno.pl:11) v:*,&,x*,x&,x$,$ ->8
作为调用entersub
的{{1}}操作系统的左侧兄弟,而您的<{p>}
print_line
已从执行流程中取出。
将此作为perlbug写下来并不会有什么坏处。
答案 1 :(得分:5)
$ perl -MO=Concise a.pl
j <@> leave[1 ref] vKP/REFC ->(end)
1 <0> enter ->2
2 <;> nextstate(main 6 a.pl:5) v:*,&,{,x*,x&,x$,$ ->3
5 <2> sassign vKS/2 ->6
3 <$> const[IV 0] s ->4
4 <0> padsv[$x:3,5] sRM*/LVINTRO ->5
6 <0> unstack v* ->7
i <2> leaveloop vK/2 ->j
7 <{> enterloop(next->b last->i redo->8) v ->e
- <1> null vK/1 ->i
h <|> and(other->8) vK/1 ->i
g <2> lt sK/2 ->h
e <0> padsv[$x:3,5] s ->f
f <$> const[IV 1] s ->g
- <@> lineseq vK ->-
- <@> scope vK ->b <---
- <0> ex-nextstate v ->8 <---
a <1> entersub[t5] vKS/TARG,2 ->b
- <1> ex-list K ->a
8 <0> pushmark s ->9
- <1> ex-rv2cv sK/2 ->-
9 <#> gv[*print_line] s/EARLYCV ->a
c <1> preinc[t2] vK/1 ->d
b <0> padsv[$x:3,5] sRM ->c
d <0> unstack v ->e
a.pl syntax OK
正在进行一些优化。 scope
被认为是不必要的并且已经过优化。 (注意“-
”意味着它永远不会到达。)
但与此同时,删除了nextstate
操作,这是设置警告和caller
的行号的原因。
因此,这是一个由于不正确的优化而导致的错误。
答案 2 :(得分:2)
我怀疑这可能是声明分隔符(分号)。您可能已经发现 - 使用您正在运行的代码,caller
报告的行号与foreach
循环相同。
所以我想发生了什么,是因为没有分号。
如果您要进行多行子通话,caller
会报告第一行:
print "first call:", __LINE__, "\n";
print "Start of statement\n",
"a bit more on line ", __LINE__, "\n",
print_line(
1,
2,
3,
5,
);
您获得了通话开始的行号,而不是结束。所以我认为这是你得到的 - 语句在分号语句分隔符出现时开始 - 这是第一个例子中的foreach
行。
因此,作为一种解决方法 - 我可能会建议您使用__LINE__
。虽然我也许建议不要太担心它太多,因为它仍然会指向你在代码中的正确位置。
如果你使用croak
,你会得到类似的东西,原因可能是同样的原因。
答案 3 :(得分:1)
正如已经指出的那样,这确实是Perl中的错误,至少可以追溯到5.10或11年,但实际上我认为更长。
据报道它是Perl错误perl #133239,尽管据称不是 难以解决,但事实并非如此。也可能不是容易容易修复,因为添加COP会减慢性能,因此性能会有所下降,并且可能需要一些管理工作来调整测试。
即使该错误已修复,也只能在Perl 5.29及更高版本中修复。这对5.10毫无帮助。
因此,这是另一种方法,它不依赖于Perl核心的更改,因此可以使用户更好地控制。但是,我会先说一下这是一个实验,除非人们愿意为此花费代码,否则它的时间不可能回到5.10。现在,我使用的最早的Perl版本是7年前的5.14,就像本文写作一样。
使用B::DeparseTree可以编写不同的代码,我认为更好的 caller()可以向您显示呼叫者的详细位置。这是您的程序经过修改以实现的目的:
#!/usr/bin/perl
use strict;
use warnings;
use B::DeparseTree::Fragment;
use Devel::Callsite;
sub dt_caller
{
my $level = $_ ? $_ : 0;
# Pick up the right caller's OP address.
my $addr = callsite($level+1);
# Hack alert 'main::main' should be replaced with the function name if not the top level. caller() is a little off-sync here.
my $op_info = deparse_offset('main::main', $addr);
# When Perl is in the middle of call, it has already advanced the PC,
# so we need to go back to the preceding op.
$op_info = get_prev_addr_info($op_info);
my $extract_texts = extract_node_info($op_info);
print join("\n", @$extract_texts), "\n";
}
foreach( my $x =0 ; $x < 1; $x++) {
print_line();
}
sub print_line {
dt_caller();
}
运行时将打印:
$ perl bug-caller.pl
print_line()
------------
dt_caller()可以并且应该包装成Carp这样的包,这样您就不会看到所有的丑陋之处。但是,我会将其留给其他人使用。我要提到的是,为了使此功能正常运行,我必须进行一些错误修复,因此,该功能仅从 B :: DeparseTree 的3.4.0版本开始起作用。