本文通过大量具有代表性和对比性的示例,来详细说明PHP中pack、unpack函数的用法。
pack(string $format, mixed ...$values): string
unpack(string $format, string $string, int $offset = 0): array|false
format字符串由格式字符组成,格式字符后面可以跟一个可选的重复参数。重复参数可以是一个整数(指定消耗多少个数据参数)或者*值(消耗到输入数据的末尾)。
对于 a, A, h, H 格式化代码,其后的数值是指定字符串长度,而不是消耗参数个数。
对于 @,其后的数字表示放置下一个数据的绝对位置。
format格式字符:
a 以 NUL 字节填充字符串
A 以 SPACE(空格) 填充字符串
h 十六进制字符串,低位在前
H 十六进制字符串,高位在前
c 有符号字符
C 无符号字符
s 有符号短整型(16位,主机字节序)
S 无符号短整型(16位,主机字节序)
n 无符号短整型(16位,大端字节序)
v 无符号短整型(16位,小端字节序)
i 有符号整型(机器相关大小字节序)
I 无符号整型(机器相关大小字节序)
l 有符号长整型(32位,主机字节序)
L 无符号长整型(32位,主机字节序)
N 无符号长整型(32位,大端字节序)
V 无符号长整型(32位,小端字节序)
q 有符号长长整型(64位,主机字节序)
Q 无符号长长整型(64位,主机字节序)
J 无符号长长整型(64位,大端字节序)
P 无符号长长整型(64位,小端字节序)
f 单精度浮点型(机器相关大小)
g 单精度浮点型(机器相关大小,小端字节序)
G 单精度浮点型(机器相关大小,大端字节序)
d 双精度浮点型(机器相关大小)
e 双精度浮点型(机器相关大小,小端字节序)
E 双精度浮点型(机器相关大小,大端字节序)
x NUL 字节
X 回退一字节
Z 以 NUL 字节填充字符串空白
@ NUL 填充到绝对位置
例子1:
$out = pack("a2", 'abcd');
echo bin2hex($out) . "\r\n"; //6162
$out = pack("aa", 'abcd', 'e');
echo bin2hex($out) . "\r\n"; //6165
$out = pack("a2a", 'abcd', 'e');
echo bin2hex($out) . "\r\n"; //616265
$out = pack("a6", 'abcd');
echo bin2hex($out) . "\r\n"; //616263640000
$out = pack("A6", 'abcd');
echo bin2hex($out) . "\r\n"; //616263642020
$out = pack("A*", 'abcd');
echo bin2hex($out) . "\r\n"; //61626364
$out = pack("A*", 'abcdef');
echo bin2hex($out) . "\r\n"; //616263646566
例子2:
$out = pack("h12", '0123456789abcdef');
echo bin2hex($out) . "\r\n"; //1032547698ba
$out = pack("H12", '0123456789abcdef');
echo bin2hex($out) . "\r\n"; //0123456789ab
$out = pack("H4", 0x11223344);
echo bin2hex($out) . "\r\n"; //2874
$out = pack("H3", '123');
echo bin2hex($out) . "\r\n"; //1230
$out = pack("H*", 'abcd');
echo bin2hex($out) . "\r\n"; //abcd
$out = pack("H*", 'abcdef');
echo bin2hex($out) . "\r\n"; //abcdef
第三个转换结果之所以是2874,是因为进行了隐式转换。
h,H格式字符对参数的期望是一个16进制字符串,而给出的是一个16进制值,其实就是整数287454020,隐式转换后变为字符串:'287454020',此字符串打包后即为前面所示结果。
第四个转换,最后4个bit位为默认值0。
例子3:
$out = pack("c2", 97, 98);
echo bin2hex($out) . "\r\n"; //6162
$out = pack("cc", 97, 98);
echo bin2hex($out) . "\r\n"; //6162
$out = pack("c2", 'a', 'b');
echo bin2hex($out) . "\r\n"; //0000
$out = pack("c2", ord('a'), ord('b'));
echo bin2hex($out) . "\r\n"; //6162
$out = pack("c2", 0x61, 0x62);
echo bin2hex($out) . "\r\n"; //6162
$out = pack("c", 0x6162);
echo bin2hex($out) . "\r\n"; //62
$out = pack("c*", 0x61, 0x62);
echo bin2hex($out) . "\r\n"; //6162
$out = pack("c*", 0x61, 0x62, 0x63);
echo bin2hex($out) . "\r\n"; //616263
$out = pack("N", 4660);
echo bin2hex($out) . "\r\n"; //00001234
$out = pack("N", 0x1234);
echo bin2hex($out) . "\r\n"; //00001234
$out = pack("N", 0x123456789abcdef0);
echo bin2hex($out) . "\r\n"; //9abcdef0
$out = pack("N", 1311768467463790320);
echo bin2hex($out) . "\r\n"; //9abcdef0
$out = pack("V", 1311768467463790320);
echo bin2hex($out) . "\r\n"; //f0debc9a
$out = pack("L", 1311768467463790320);
echo bin2hex($out) . "\r\n"; //f0debc9a
$out = pack("l", 1311768467463790320);
echo bin2hex($out) . "\r\n"; //f0debc9a
$out = pack("N", 0xf23456789abcdef0);
echo bin2hex($out) . "\r\n"; //9abce000
PHP不支持无符号数,且整数只有一种,不分长短。
打包时,“无符号”跟“有符号”的格式字符是没有区别的(解包时有区别)。
字符、16位整形、32位整形、64位整形都是类似的:它们期待的参数都是整数,且都是从整数的低字节到高字节,取自己所要的字节数打包。
16位整形、32位整形、64位整形涉及多字节,打包时有字节排列顺序的区别(个人通常用大端字节序)。
细心的读者可能已经发现,最后一个结果似乎跟上面规律不一致,其实这里包含两次隐式转换,第一次是0xf23456789abcdef0(17452669531959647984)作为一个值已经超出了整数类型范围(±9223372036854775807,64位系统),PHP自动将其以浮点数形式保存,可通过var_dump(0xf23456789abcdef0)验证,而参数要求的是一个整数,又进行了一次强制类型转换,此次转换结果为int(-994074541749903360),即0xf23456789abce00,至于为什么是这么多,可以去了解下int与float的存储与转换处理,这里就不多说,到这里就已经符合上面的规律了。
例子4:
$out = pack("x2");
echo bin2hex($out) . "\r\n"; //0000
$out = pack("a2", "");
echo bin2hex($out) . "\r\n"; //0000
$out = pack("cccc", 0x61, 0x62, 0x63, 0x64);
echo bin2hex($out) . "\r\n"; //61626364
$out = pack("ccxcc", 0x61, 0x62, 0x63, 0x64);
echo bin2hex($out) . "\r\n"; //6162006364
$out = pack("ccXcc", 0x61, 0x62, 0x63, 0x64);
echo bin2hex($out) . "\r\n"; //616364
$out = pack("a2Za2", 'zz', 'abcdefg', 'zz');
echo bin2hex($out) . "\r\n"; //7a7a007a7a
$out = pack("a2Z2a2", 'zz', 'abcdefg', 'zz');
echo bin2hex($out) . "\r\n"; //7a7a61007a7a
$out = pack("a2Z3a2", 'zz', 'abcdefg', 'zz');
echo bin2hex($out) . "\r\n"; //7a7a6162007a7a
$out = pack("a2Z3a2", 'zz', 'a', 'zz');
echo bin2hex($out) . "\r\n"; //7a7a6100007a7a
$out = pack("a2Z*a2", 'zz', 'abcdefg', 'zz');
echo bin2hex($out) . "\r\n"; //7a7a61626364656667007a7a
$out = pack("a5@3a5", 'abcdefg', 'hijklmn');
echo bin2hex($out) . "\r\n"; //61626368696a6b6c
$out = pack("a2@3a5", 'abcdefg', 'hijklmn');
echo bin2hex($out) . "\r\n"; //61620068696a6b6c
pack("x2")等价于pack("a2", "")。
Z格式字符的作用是将参数截断成指定长度的字符串,并打包。截出的字符串以NUL结尾,NUL包含在指定长度内。如果参数长度不够则在末尾以NUL填充。
@格式字符的作用是指定放置下一个参数的起始位置,如果该位置小于已放置的长度,则该位置后面的已放置数据将截断丢弃,如果该位置大于已放置长度,则填充NUL直到该位置。
例子5:
var_dump(unpack("c3", "0123456789abcdef"));
输出:
array(3) {
[1]=>
int(48)
[2]=>
int(49)
[3]=>
int(50)
}
var_dump(unpack("a2", "0123456789abcdef"));
输出:
array(1) {
[1]=>
string(2) "01"
}
var_dump(unpack("c3a2", "0123456789abcdef"));
输出:
array(3) {
["a21"]=>
int(48)
["a22"]=>
int(49)
["a23"]=>
int(50)
}
把a2当做c2的key了
var_dump(unpack("c3/a2", "0123456789abcdef"));
输出:
array(3) {
[1]=>
string(2) "34"
[2]=>
int(49)
[3]=>
int(50)
}
把c3的结果[48,49,50]与a2的结果["34"]进行了数组合并,所以需要解出多个不同类型参数的,应该按照下面的示例用“/”分割并都指定key。
var_dump(unpack("c3ka/a2kb", "0123456789abcdef"));
输出:
array(4) {
["ka1"]=>
int(48)
["ka2"]=>
int(49)
["ka3"]=>
int(50)
["kb"]=>
string(2) "34"
}
var_dump(unpack("a2", "0123456789abcdef", 4));
输出:
array(1) {
[1]=>
string(2) "45"
}
var_dump(unpack("H*", "0123456789abcdef", 4));
输出:
array(1) {
[1]=>
string(24) "343536373839616263646566"
}
var_dump(unpack("h*", "0123456789abcdef", 4));
输出:
array(1) {
[1]=>
string(24) "435363738393162636465666"
}
var_dump(unpack("c3ka/x2/a2kb", "0123456789abcdef"));
输出:
array(4) {
["ka1"]=>
int(48)
["ka2"]=>
int(49)
["ka3"]=>
int(50)
["kb"]=>
string(2) "56"
}
var_dump(unpack("c3ka/X2/a2kb", "0123456789abcdef"));
输出:
array(4) {
["ka1"]=>
int(48)
["ka2"]=>
int(49)
["ka3"]=>
int(50)
["kb"]=>
string(2) "12"
}
var_dump(unpack("c3ka/@2/a2kb", "0123456789abcdef"));
输出:
array(4) {
["ka1"]=>
int(48)
["ka2"]=>
int(49)
["ka3"]=>
int(50)
["kb"]=>
string(2) "23"
}
var_dump(unpack("c3ka/@8/a2kb", "0123456789abcdef"));
输出:
array(4) {
["ka1"]=>
int(48)
["ka2"]=>
int(49)
["ka3"]=>
int(50)
["kb"]=>
string(2) "89"
}
例子6:
$out = unpack("a*", pack("a4", 'ab'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(4) "ab��"
}
61620000
$out = unpack("a*", pack("A4", 'ab'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(4) "ab "
}
61622020
$out = unpack("a*", pack("A4", 'ab' . chr(9)));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(4) "ab "
}
61620920
$out = unpack("a*", pack("A3xA", 'ab', 'c'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(5) "ab �c"
}
6162200063
$out = unpack("A*", pack("a4", 'ab'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(2) "ab"
}
6162
$out = unpack("A*", pack("A4", 'ab'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(2) "ab"
}
6162
$out = unpack("A*", pack("A4", 'ab' . chr(9)));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(2) "ab"
}
6162
$out = unpack("A*", pack("A3xA", 'ab', 'c'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(5) "ab �c"
}
6162200063
$out = unpack("Z*", pack("a4", 'ab'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(2) "ab"
}
6162
$out = unpack("Z*", pack("A4", 'ab'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(4) "ab "
}
61622020
$out = unpack("Z*", pack("A4", 'ab' . chr(9)));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(4) "ab "
}
61620920
$out = unpack("Z*", pack("A3xA", 'ab', 'c'));
var_dump($out);
echo bin2hex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
string(3) "ab "
}
616220
a对解包后的字符串不作任何处理。
A会去除解包后字符串末尾的空格(0x20)、TAB(0x09)、回车(0x0D)、换行(0x0A)、NUL(0x00)字符。
Z则是从第一个NUL字符截断。
例子7:
$out = unpack("l", pack("H*", 'feffffffffffffff'));
var_dump($out);
echo dechex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
int(-2)
}
fffffffffffffffe
$out = unpack("L", pack("H*", 'feffffffffffffff'));
var_dump($out);
echo dechex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
int(4294967294)
}
fffffffe
其实就是00000000fffffffe
$out = unpack("q", pack("H*", 'feffffffffffffff'));
var_dump($out);
echo dechex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
int(-2)
}
fffffffffffffffe
$out = unpack("Q", pack("H*", 'feffffffffffffff'));
var_dump($out);
echo dechex($out[1]) . "\r\n";
输出:
array(1) {
[1]=>
int(-2)
}
fffffffffffffffe
整数相关解包时,根据格式字符取相应字节数,从整数结构的低字节开始原样填入,如果未填满(8byte),则根据格式字符的定义看带不带符号,如果带则用1补满,否则用0补满。