Perl 的 index() 的奇怪行为当用空 substr 调用时 vs 没有 Encode::decode()

Perl's odd behavior of index() when called with empty substr with vs without Encode::decode()

我在下面创建了一个非常小的示例代码来说明 Perl 的 index() 函数的 return 值如何针对传递或未传递的字符串上的空 substr ("") 进行更改通过 Encode::decode().

use strict;
use Encode;

my $mainString = (@ARGV >= 2) ? $ARGV[1] : "abc";
my $subString  = (@ARGV >= 3) ? $ARGV[2] : "";
if (@ARGV >= 1) {
    $mainString = Encode::decode("utf8", $mainString);
}

my $position = index($mainString, $subString, 0);
my $loopCount = 0;
my $stopLoop  = 7; # It goes for ever so set a stopping value
while ($position >= 0) {
    if ($loopCount >= $stopLoop) {
        last;
    }
    $loopCount++;

    print "[$loopCount]: $position \"$mainString\" [".length($mainString)."] ($subString)\n";
    $position = index($mainString, $subString, $position + 1);
}

在进入有 vs 没有 Encode::decode() 之前,index() 的 return 值应该是空的 substr ("") 因为 [=27= 】 不提了。虽然它没有提到它,但这里是执行结果 without calling Encode::decode() for ASCII characters "abc" (@ARGV = 0):

>perl StringIndex.pl
[1]: 0 "abc" [3] ()
[2]: 1 "abc" [3] ()
[3]: 2 "abc" [3] ()
[4]: 3 "abc" [3] ()
[5]: 3 "abc" [3] ()
[6]: 3 "abc" [3] ()
[7]: 3 "abc" [3] ()

但是,当涉及编码时,return 值会发生变化。 return 值的变化就好像正在搜索的字符串在调用 with Encode::decode() 时不受其长度限制,用于 ASCII 字符“abc”($ARGV[0] = 1 ):

>perl StringIndex.pl 1
[1]: 0 "abc" [3] ()
[2]: 1 "abc" [3] ()
[3]: 2 "abc" [3] ()
[4]: 3 "abc" [3] ()
[5]: 4 "abc" [3] ()
[6]: 5 "abc" [3] ()
[7]: 6 "abc" [3] ()

旁注:

  1. substr在上面的例子中设置为空字符串(“”),但在我的真实程序中它是一个根据条件改变值的变量。
  2. 我明白最简单的解决办法是检查substr是否为空并且不进入while循环
  3. 我正在使用“这是 perl 5,版本 28,subversion 1 (v5.28.1) 内置 对于 MSWin32-x64-多线程

这将被视为一个错误,我已经报告了 here


要重现的最少代码:

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

my $s = "abc";
my $len = length($s);
utf8::upgrade($s);
length($s) if $ARGV[0];
say index($s, "", $len+1);
$ perl a.pl 0
3

$ perl a.pl 1
4

Perl 有两种字符串存储格式。 “升级”格式和“降级”格式。

Encode::decode 总是 return 一个升级的字符串。 utf8::upgrade 告诉 Perl 切换标量使用的存储格式。

降级字符串的每个字符都可以存储 0 到 255 之间的数字。字符串的每个字符都存储为适当值的字节。如果您有字节或 ASCII 文本,这当然很好。但这对于任意文本是不够的。

升级字符串的每个字符可以存储0到232-1或0到264-1之间的数字取决于你的 Perl 是如何编译的。这足以存储任何 Unicode 代码点(甚至是 BMP 之外的代码点)。每个字符都使用“utf8”编码,UTF-8 的非标准扩展。

utf8(类似于UTF-8)是可变长度编码。这会带来两个问题:

  • 确定升级字符串的长度需要遍历整个字符串。
  • 确定字符在升级后的字符串中的位置需要遍历整个字符串。

让我们考虑以下片段:

index($str, $substr, $pos)

使用降级字符串,index可以直接跳转到$pos指示的位置。一道简单的指针运算题。

但是因为升级字符串的每个字符可能需要不同的存储量,index 无法使用指针算法找到位置 $pos 处的字符。如果不进行优化,对 index 的每次使用都必须从偏移量 0 开始并在字符串中移动,直到找到 $pos.

指示的字符

那将是不幸的。想象一下,如果在循环中使用 index 来查找所有匹配项。所以 Perl 对此进行了优化!当升级字符串的长度已知时,Perl 将其附加到标量。

$ perl -MDevel::Peek -e'
   $s = "abc";
   utf8::upgrade($s);
   Dump($s);
   length($s);
   Dump($s);
'
SV = PV(0x56483dda7e80) at 0x56483ddd5ba0
  REFCNT = 1
  FLAGS = (POK,pPOK,UTF8)
  PV = 0x56483ddda3f0 "abc"[=13=] [UTF8 "abc"]
  CUR = 3
  LEN = 10
SV = PVMG(0x56483de0ecf0) at 0x56483ddd5ba0
  REFCNT = 1
  FLAGS = (SMG,POK,pPOK,UTF8)
  IV = 0
  NV = 0
  PV = 0x56483ddda3f0 "abc"[=13=] [UTF8 "abc"]
  CUR = 3
  LEN = 10
  MAGIC = 0x56483ddd4050
    MG_VIRTUAL = &PL_vtbl_utf8
    MG_TYPE = PERL_MAGIC_utf8(w)
    MG_LEN = 3                           <-- Attached length

同样,字符的偏移有时也会附加到标量上!

$ perl -MDevel::Peek -e'
   $s = "abc";
   utf8::upgrade($s);
   Dump($s);
   index($s, "", 2);
   Dump($s);
'
SV = PV(0x558d5c970e80) at 0x558d5c99ebc0
  REFCNT = 1
  FLAGS = (POK,pPOK,UTF8)
  PV = 0x558d5c9ae3a0 "abc"[=14=] [UTF8 "abc"]
  CUR = 3
  LEN = 10
SV = PVMG(0x558d5c9d7d10) at 0x558d5c99ebc0
  REFCNT = 1
  FLAGS = (SMG,POK,pPOK,UTF8)
  IV = 0
  NV = 0
  PV = 0x558d5c9ae3a0 "abc"[=14=] [UTF8 "abc"]
  CUR = 3
  LEN = 10
  MAGIC = 0x558d5c9af690
    MG_VIRTUAL = &PL_vtbl_utf8
    MG_TYPE = PERL_MAGIC_utf8(w)
    MG_LEN = -1
    MG_PTR = 0x558d5c99cb80
       0: 2 -> 2                     <-- Attached character offset
       1: 0 -> 0                     <-- Attached character offset

行为的差异是由于不同的代码是基于字符串格式和缓存的信息被执行的代码中的路径。