【问题标题】:Hidden bugs with given-when and for-match. Is Perl truly cross-platform?给定时间和匹配的隐藏错误。 Perl 真的是跨平台的吗?
【发布时间】:2022-02-07 04:59:31
【问题描述】:

我一直在尝试制作一个相对较大的 Perl 程序,该程序多年来一直在 CentOS 上运行良好,以在 Ubuntu 上运行,这已成为一场巨大的噩梦。 CentOS 使用为 x86_64-linux-thread-multi 构建的 Perl,而 Ubuntu 使用 x86_64-linux-gnu-thread-multi 构建。 AFAIK,当程序调用相同的先前版本v5.10.1 时,两种环境中的解释器行为应该相同。然而我的行为却截然不同,包括关于given/whensmartmatch 的警告是实验性的,最重要的是,还有一组难以追踪和解决的讨厌的错误。当下面显示的given 语句(表格 1)匹配并调用函数时,会出现一个特殊问题。然后突然间,从未以其他方式接触过的开关变量(称为$ailtype)的值从内存中删除!如果我简单地调用该函数,则不会发生任何令人讨厌的事情。所以,我用for 语句(表格2)替换了given/when 用法,我的问题是为什么问题仍然存在?!真正避免该问题的唯一形式是if/elsifs 的简单链(形式 3),这清楚地表明问题出在形式 1 和 2 以及 perl 解释器不一致且漏洞百出:它甚至不会对表格 2 产生“实验性”警告。

这是表格1(原件):

print "ailtype is $ailtype \n"; # prints "ailtype is 8"

given ($ailtype) {
    when (4) { &parse_mascot}
    when (5) { &parse_sequest}
    when (8) { &parse_spectrast($ms2_results, $rttemp)}
    when (9) { &parse_cnstab($ms2_results, $rttemp)}
    default { STDOUT->autoflush(1) and die "ailtype=|$ailtype| unknown.\n"; }
}

print "ailtype is $ailtype \n"; # prints "ailtype is ". $ailtype got destroyed!

prints 用于调试。我可以将它们放在when 块中,并确认$ailtype&parse_spectrast 函数调用后被销毁。但是,该功能根本不会读取或触摸$ailtype! (有趣的是,如果我进入函数内部并打印$ailtype 的值以准确找到它被搞砸的地方,我发现它发生在解析输入文件行的while 循环中。以某种方式打印$ailtype返回该文件的行!)

该程序由几个带有许多 given/when 语句的大型 perl 文件组成,并且手动重写所有这些语句会很乏味。我必须确保替代形式有效。所以我尝试了表格2(建议here):

print "ailtype is $ailtype \n"; # prints "ailtype is 8"

for ($ailtype) {
    /4/ and do { &parse_mascot; last};
    /5/ and do { &parse_sequest; last};
    /8/ and do { &parse_spectrast($ms2_results, $rttemp); last};
    /9/ and do { &parse_cnstab($ms2_results, $rttemp); last};
    do { STDOUT->autoflush(1) and die "ailtype=|$ailtype| unknown.\n"; }
}

print "ailtype is $ailtype \n"; # prints "ailtype is ". $ailtype still gets destroyed!

这种形式还是出现问题,我真的不明白为什么?!解释器不再在此处警告实验性given/when(尽管它们仍在代码的其他地方使用。我确保它们没有出现在有问题的块之前并且没有帮助)。

不管你是否感到惊讶,if/elsifs 链(表格 3)工作正常:

print "ailtype is $ailtype \n"; # prints "ailtype is 8"

if    (4 == $ailtype) { 
    &parse_mascot;
}
elsif (5 == $ailtype) { 
    &parse_sequest;
}
elsif (8 == $ailtype) { 
    &parse_spectrast($ms2_results, $rttemp); 
}
elsif (9 == $ailtype) { 
    &parse_cnstab($ms2_results, $rttemp);
}
else { 
    STDOUT->autoflush(1) and die "ailtype=|$ailtype| unknown.\n";
}

print "ailtype is $ailtype \n"; # prints "ailtype is 8". It was never changed.

但我认为 Perl 的强大功能可以让我们不必编写所有这些代码。如果我确定我有一个可靠的口译员,我会愿意这样做。将版本更改为 v5.16.3(两个平台上安装的最新版本)只会产生关于声明的新错误,“不允许使用裸字 STDOUT”等。单独与这个错误斗争了 10 个小时,我非常怀疑。

【问题讨论】:

  • 你调用的潜艇是做什么的?你怎么能问这样的问题而不包括这些信息?您应该包含完整且最少的代码,可以运行以演示您的问题。
  • @TLP 他们做了很多事情,而且太大了,不能在这里发帖。但正如我所说,他们根本不阅读或更改$ailtype。一次也没提过。简单地调用问题点函数或使用if 可以避免问题的事实应该清楚地表明,函数的作用并不重要。
  • @TLP 我会尝试制作一个最小的独立代码来重现问题。
  • “当程序调用相同的先前版本v5.10.1时,解释器行为在两种环境中应该是相同的”。不清楚你的意思。如果您认为use v5.10.1 将使它使用这个版本的Perl - 它不会。它只确保您运行的是至少 5.10.1,并且它会开启5.10.1 引入的一些功能。
  • 我忍不住要这么说:我们需要说多少次given-when和smartmatch(尤其是!)有严重的问题,他们将被改变,也许无法识别(或更糟)?繁琐与否,只需替换那些代码部分,并小心使用尽可能简单和最清晰的代码(这些重载功能的问题的一部分,它们悄悄地引入了各种假设)。对不起。它发生了。

标签: perl switch-statement match given


【解决方案1】:

for 为循环中的每个值别名 $_。如果被调用的函数改变了$_ 的值,那么原来的变量也会改变。在 while 循环中使用的菱形运算符会更改全局 $_,并在 eof 上变为 undef

#!/usr/bin/perl
use warnings;
use strict;

sub f {
    while (<DATA>) {
        warn "read:\t$_";
    }
}

my $x = 8;

print "Before:<<$x>>\n";

for ($x) {
    &f();
}

print "After:<<$x>>\n";

__DATA__
A
B

解决方案: 在while (&lt;&gt;) { 之前插入以下行:

    local $_;

这将在离开local 语句的范围时恢复$_ 的原始值。

【讨论】:

  • 你完全正确!非常感谢!现在,为什么这种行为依赖于平台? (或者是版本问题,注意到 Steffen Ullrich 的评论?)
  • @FNia 它不依赖于平台。我怀疑这可能与使用的词汇 $_ given 有关。就像given/when,这是一个失败的实验功能。它实际上已从 Perl 中删除(就像他们想对 given/when 做的那样)
  • @ikegami 哦,你的意思是整个词法$_ 已经被删除了?!还是只是全局形式?你能链接文档吗?这会破坏很多代码。而且我还想知道何时引入了有关while (&lt;&gt;) 循环中的全局$_ 发生了什么变化,或者for () 是使用全局还是本地$_?一般的安全做法是什么?
  • @FNia my $_ 不见了。 given(隐式)使用了它。 for 像往常一样使用(本地化)全局 $_
  • @FNia 这实际上不是关于$_,而是关于for,类似的是将循环变量别名为列表中的命名变量。 for my $foo ($x) { $foo = 0 } 将覆盖 $x,就像 for ($x) { $_ = 0 } 一样。
猜你喜欢
  • 1970-01-01
  • 2013-02-26
  • 1970-01-01
  • 2014-10-15
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-31
相关资源
最近更新 更多