`use utf8`为何会改变Perl中`Encode`模块的解码语义?
排查数据库程序宽字符问题时,发现以下异常现象:
use utf8; use Encode; my $x = "ö"; my $decoded = Encode::decode('UTF-8', $x); my $encoded = Encode::encode('UTF-8', $decoded); my $redecoded = Encode::decode('UTF-8', $encoded); { use bytes; printf "original: %vx\n", $x; printf "decoded: %vx\n", $decoded; printf "encoded: %vx\n", $encoded; printf "redecoded: %vx\n", $redecoded; }
执行脚本后输出:
original: c3.b6 decoded: ef.bf.bd encoded: ef.bf.bd redecoded: ef.bf.bd
0xef 0xbf 0xbd是UTF-8的替换字符(�),并非原字符的有效编码。移除use utf8;语句后,输出符合预期:
original: c3.b6 decoded: c3.b6 encoded: c3.b6 redecoded: c3.b6
为何use utf8;会改变解码语义?
核心差异在于use utf8;对字符串字面量的处理逻辑:
没有
use utf8;时:
脚本中的字符串"ö"会被Perl当作字节序列处理,也就是直接存储文件编码(假设你的脚本文件本身是UTF-8编码)对应的c3.b6字节。此时调用Encode::decode('UTF-8', $x),是把这串合法的UTF-8字节解码成Perl内部的Unicode字符串,再用Encode::encode('UTF-8', ...)编码回去,自然和原字节一致,所以输出全部是c3.b6。加上
use utf8;时:
这个指令告诉Perl:当前脚本的源代码是UTF-8编码,所以字符串字面量"ö"会被Perl自动解码成内部的Unicode字符(U+00F6)。此时$x已经是Perl的Unicode字符串,不是原始字节序列了。
这时再调用Encode::decode('UTF-8', $x)就出问题了:decode函数的作用是把字节序列转成Unicode字符串,但你传入的已经是Unicode字符串。Perl会先把这个Unicode字符串降级成字节序列(默认用Latin-1编码),也就是把U+00F6转成字节0xF6。然后尝试用UTF-8解码0xF6——这是一个无效的UTF-8字节(单独的0xF6不符合UTF-8的编码规则),所以Encode模块会用替换字符U+FFFD(对应UTF-8字节ef.bf.bd)来替代错误,后续的编码、再解码自然都是这个替换字符的字节序列。
简单说,use utf8;让字符串字面量变成了Unicode字符串,而你错误地对一个已经是Unicode的字符串调用了decode,导致了无效编码的错误处理。
内容的提问来源于stack exchange,提问作者choeger

