首页›文章›perl pack函数详解

perl pack函数详解

计算机技术 · perl 程序开发2013-09-030次浏览0 个评论

pack函数用法如下:

perl
pack TEMPLATE, LIST

pack接收一个普通 Perl 数值的 LIST 并且根据 TEMPLATE 把它们转换成一个字节串并且返回 该字串。参数列表在必要时会被填充或者截除。也就是说,如果你提供的参数比 TEMPLATE 要求的 少,pack 假设缺的都是空值。如果你提供的参数比 TEMPLATE 要求的多,那么多余的参数被忽略。 在 TEMPLATE 里无法识别的格式元素将会抛出一个例外。
这个模板把该字串的结构描述成一个域序列。每个域都由一个字符代表,描述该值的类型和其编码。 比如,一个格式字符 N 声明一个四个字节的无符号整数,字节序是大头在前。
域是以模板中给出的顺序包装的。比如,如果要把一个一字节的无符号整数和一个单精度浮点数值 包装到一个字串里,你要说:

perl
$string = pack("CF", 244, 3.14);

返回的字串的第一个字节的值是 244。剩下的字节是 3.14 作为单精度浮点数的编码。浮点数的 具体编码方式取决于你的计算机的硬件。
有些包装时要考虑的重要事情是:
数据的类型(比如是整数还是浮点还是字串),
数值的范围(比如你的整数是否放在一个,两个,四个,或者甚至八个字节里;或者你包装的是一个 8 位字符还是 Unicode 字符。)
你的整数是有符号还是无符号,以及使用的编码(比如说本机,包装位和字节时小头在前,或者是大头在前)。
pack/unpack 的摸板字符:

字符含义
a一个填充空的字节串
A一个填充空格的字节串
b一个位串,在每个字节里位的顺序都是升序
B一个位串,在每个字节里位的顺序都是降序
c一个有符号 char(8位整数)值
C一个无符号 char(8位整数)值;关于 Unicode 参阅 U
d本机格式的双精度浮点数
f本机格式的单精度浮点数
h一个十六进制串,低四位在前
H一个十六进制串,高四位在前
i一个有符号整数值,本机格式
I一个无符号整数值,本机格式
l一个有符号长整形,总是 32 位
L一个无符号长整形,总是 32 位
n一个 16位短整形,“网络”字节序(大头在前)
N一个 32 位短整形,“网络”字节序(大头在前)
p一个指向空结尾的字串的指针
P一个指向定长字串的指针
q一个有符号四倍(64位整数)值
Q一个无符号四倍(64位整数)值
s一个有符号短整数值,总是 16 位
S一个无符号短整数值,总是 16 位
u一个无编码的字串
U一个 Unicode 字符数字
v一个“VAX”字节序(小头在前)的 16 位短整数
V一个“VAX”字节序(小头在前)的 32 位短整数
w一个 BER 压缩的整数
x一个空字节(向前忽略一个字节)
X备份一个字节
Z一个空结束的(和空填充的)字节串
@用空字节填充绝对位置

你可以在你的 TEMPLATE 里自由地使用空白和注释。注释以惯用的 # 符号开头并且延伸到 TEMPLATE 里第一个换行符(如果存在)。
每个字母后面都可以跟着一个数字,表示 count(计数),解释成某种形式的重复计数或者长度, 具体情况取决于格式。除了a,A,b,B,h,H,P,和 Z 之外,所有格式的 count 都是重复次数, 因此 pack 从 LIST 里吃进那么多数值。如果 count 是一个 * 表示剩下的所有东西。
a,A 和 Z 格式只吃进一个数值,但是把它当作一个长度为 count 的字节串打包,并根据需要 填充空或者空格。在解包的时候,A 抽去结尾的空格和空,Z 抽去第一个空后面的所有东西,而 a 原封不动地返回文本数据。打包的时候,a 和 Z 是相同的。
与之类似,b 和 B 格式打包一个长度为 count 的位串。输入域里的每个字节都以每个输入字节的 最低位(也就是 ord($byte) % 2)为基础生成结果中的 1 个位。方便一点来说,这意味着字节 0 和 1 生成位 0 和 1。从输入字串的开头开始,每 8 个字节转换成一个字节的输出。如果输入字串 的长度不能被 8 整除,那么余下的部分用 0 补齐。类似,在 uppack 的时候,任何额外的位都被 忽略。如果输入字串比需要的长,那么多出来的部分被忽略。count 如果是 * 意思是使用输入域的 所有字节。在解包的时候,这些位转换成一个 0 和 1 组成的字串。
h 和 H 格式打包一个由 count 个半字节(4 位组,常用于代表十六进制位。)组成的字串。
p 格式打包一个指向一个空结尾的字串。你有责任确保该字串不是一个临时数值(因为临时值可能 在你能使用打包的结果之前很可能被释放掉)。P 格式打包一个指向一个结构的指针,该结构的 大小由 count 指明。如果对应的 p 或 P 的值是 undef,则创建一个空指针。
/ 字符允许对这样一个字串进行打包或解包:这个打了包的结构包含一个字节数以及后面跟着字串 本身。你可以这样写 length-item/string-item。length-item 可以是任意 pack 模板字符, 并且描述长度值是如何打包的。最常用的可能是那些整数打包的东西,比如 n(用于打包 Java 字串),w(用于打包 ASN.1 或 SNMP)以及 N(用于 Sun XDR)。string-item 目前必须是 A,a,或者 Z。对于 unpack 而言,字串的长度是从 length-item 里获取的,但是如果你放 进去,那么它将被忽略。

perl
unpack 'C/a',   "\04Gurusamy";      # 生成 'Guru'
uppack 'a3/A* A*', '077 Bond J ';      # 生成 (' Bond', 'J')
pack 'n/a* w/a*', 'hell', ','world';      #  生成 "hello, world"

length-item 不会从 unpack 明确地返回。向 length-item 字母加一个 count 不一定能干有用 的事,除非该字母是 A,a,或 Z。用带 length-item 的 a 或 Z 进行打包可能会引入空(\0) 字符,这时候,Perl 在会认为它不是合法数字字串。
整数格式 s,S,l,和 L 的后面可以紧跟一个 !,表示本机的短整数或者长整数,而不是各自准确 的 16 位和 32 位。现在,这是一个在许多 64 位平台上的问题,在这些平台上本地 C 编译器看到 本机短整数和长整数可能和上面的这些值不同。(i! 和 I! 也可以用,不过只不过是为了保持 完整;它们与 i 和 I 相同。)
你可以通过 Config 模块获取制作你的 Perl 的平台上的本机的 short,int,long 和 long long 的实际长度:

perl
use Config;
print $Config{shortsize},      "\n";
print $Config{intsize},      "\n";
print $Config{longsize},      "\n";
print $Config{longlongsize},   "\n";

这里只是说 Configure 知道一个 long long 的大小,但着并不意味着你就能用 q 和 Q。(有些 系统能有,不过你用的系统很可能还没有。)
长度大于一个字节的整数格式(s,S,i,I,l,和 L)都是天生不能在不同处理器之间移植的, 因为它们要遵从本机字节序和位权重序的规则。如果你需要可移植的整数,那么使用格式 n,N, v,和 V;因为它们是字节权重序并且是尺寸已知的。
浮点数只以本机格式存在。因为浮点格式的千差万别而且缺乏一种标准的“网络”表现形式,所以 没有做任何浮点数交换的工具。这意味着在一台机器上打包了的浮点数数据可能不能在另外一台上 读。甚至如果两台机器都使用 IEEE 浮点数算法的话,这都仍然是一个问题,因为与权重相关的 内存表现形式不是 IEEE 规范的一部分。
Perl 在内部使用双精度数进行所有浮点数计算,所以从 double 转换成 float,然后又转换成 float 会损失精度。这就意味着 unpack("f", pack("f", $foo)) 可能不等于 $foo。
你有责任为其他程序考虑任何对齐或填充的问题,尤其是那些带着 C 编译器自己的异质概念的 C struct 的程序,C 编译器在不同的体系结构上对 C struct 如何布局有着巨大的差别。你可能 需要在打包时增加足够的 x 来弥补这个问题。比如,一个 C 声明:

c
struct foo {
    unsigned char c;
    float f;
};

可以写成一个“C x f”格式,一个“C x3 f”格式,或者甚至是“f C”格式——而且这只是其中 一部分。pack 和 unpack 函数把它们的输入和输出当作平面的字节序列处理,因为它们不知道 这些字节要去哪儿,或者从哪儿来。
让我们看一些例子,下面的第一部分把数字值包装成字节:

perl
$out = pack "CCCC", 65, 66, 67, 68;      # $out 等于"ABCD"
$out = pack "C4", 65, 66, 67, 68;         # 一样的东西

下面的对 Unicode 的循环字母做同样的事情:

perl
$foo = pack("U4", 0x24b6, 0x24b7, 0x24b8, 0x24b9);

下面的做类似的事情,增加了一些空:

perl
$out = pack "CCxxCC", 65, 66, 67, 68;      # $out 等于 "AB\0\0CD"

打包你的短整数并不意味着你就可移植了:

perl
$out = pack "s2", 1, 2;         # 在小头在前的机器上是 "\1\0\2\0"

在二进制和十六进制包装上,count 指的是位或者半字节的数量,而不是生成的字节数量:

perl
$out = pack "B32", "...(略)";
$out = pack "H8", "5065726c";         # 都生成"Perl"

a 域里的长度只应用于一个字串:

perl
$out = pack "a4", "abcd", "x", "y", "z";      # "abcd"

要绕开这个限制,使用多倍声明:

perl
$out = pack "aaaa",    "abcd", "x", "y", "z";   # "axyz"
$out = pack "a" x 4,   "abcd", "x", "y", "z";   # "axyz"

a 格式做空填充:

perl
$out = pack "a14", "abcdefg";         # " abcdefg\0\0\0\0\0\0"

这个模板打包一个 C 的 struct tm 记录(至少在一些系统上如此):

perl
$out = pack "i9pl", gmtime(), $tz, $toff;

通常,同样的模板也可以在 unpack 函数里使用,尽管一些模板的动作不太一样,特别是 a,A, 和 Z。
如果你想把定长文本域连接到一起,可以用 TEMPLATE 是多个 a 或者 A 的 pack:

perl
$string = pack("A10" x 10, @data);

如果你想用一个分隔符连接变长文本域,那么可以用 join 函数:

perl
$string = join(" and ", @data);
$string = join ("", @data);         #  空分隔符

尽管我们所有的例子都使用文本字串做模板,但我们没有理由不让你使用来自磁盘文件的模板。 你可以基于这个函数做一个完整的关系型数据库。(我们不会想知道那样能证明你什么东西。)

评论

0

评论加载中…

发表评论

0/2000