编程 PHP 8.6 的 pack()/unpack() 端序修饰符:用 l>、l< 直接指定大小端

2026-11-09 21:06:15

PHP 8.6 的 pack()/unpack() 端序修饰符:用 l>、l< 直接指定大小端

PHP 8.6 的 New Features 里有一项:给 pack() / unpack() 增加的端序修饰符。

参考:PHP 8.6 migration — new features

之前的问题:默认是机器字节序

pack() / unpack() 里的 l、L、q、Q、f、d,读写的是机器字节序,即由运行 PHP 的这台机器决定。

结果就是代码在 x86 上跑对了,换到大端机器、或者某些嵌入式目标上,同样的字节流解析出来就是错的。浮点更麻烦:f 和 d 根本没有端序可选项,想换个字节序没有现成语法。

新写法

<(小端)和 >(大端)作为修饰符,接在格式字符后面:

pack('l>', 1);   // "\x00\x00\x00\x01"
pack('l<', 1);   // "\x01\x00\x00\x00"

unpack() 同理,修饰符接在格式字符后、字段名之前:

$b = "\x00\x00\x00\x01";
unpack('l>value', $b)['value'];   // 1

适用范围:整数 s、S、l、L、q、Q,以及浮点 f、d。

和老写法对照

以前要显式端序,只能用 n/v(16 位)和 N/V(32 位)这几个替代格式字符。64 位整数与 float/double 没有替代字符,只能 strrev(pack(...)) 拼。

数据位宽旧写法(显式端序)8.6 新写法
s / S16 位n 大端 / v 小端s> s< / S> S<
l / L32 位N 大端 / V 小端l> l< / L> L<
q / Q64 位无,需 strrev()q> q< / Q> Q<
f / d32 / 64 位浮点无,需 strrev()f> f< / d> d<

32 位大端整数:

// 旧写法
pack('N', $len);
// 8.6
pack('L>', $len);

64 位小端整数与双精度大端浮点:

// 旧写法
strrev(pack('q', $id));
strrev(pack('d', $x));

// 8.6
pack('q<', $id);
pack('d>', $x);

实际场景:解析二进制协议头

假设协议头是 8 字节:前 4 字节 magic,后 4 字节是大端无符号 32 位长度字段。

旧写法用 N:

$header = fread($fp, 8);
if (strlen($header) !== 8) {
    throw new RuntimeException('short header');
}

$magic  = substr($header, 0, 4);
$length = unpack('N', substr($header, 4, 4))[1];

8.6 用 L>:

$header = fread($fp, 8);
if (strlen($header) !== 8) {
    throw new RuntimeException('short header');
}

$magic  = substr($header, 0, 4);
$length = unpack('L>len', substr($header, 4, 4))['len'];

同样的修饰符可以直接用在写 PNG chunk 的长度字段上(PNG 规范里 chunk 长度就是 4 字节大端无符号):

$chunk = pack('L>', strlen($data)) . 'IDAT' . $data;

读硬件、串口报文时也是同一套:报文里字段是大端还是小端,照协议写 > 或 < 就行,不用再关心跑在什么机器上。

注意点

符号位和无符号仍然由格式字符决定。 修饰符只负责端序,不改变符号语义:l 是有符号 32 位,L 是无符号 32 位;l> 是有符号大端,l< 是有符号小端,依此类推。写协议里明确的无符号字段时别顺手用了 l。

不要把机器字节序和显式端序混用。 同一个结构里,有的字段用不带修饰符的 l(机器序),有的用 L>(显式大端),换机器后前者会变、后者不会,出问题时很难定位。既然开始指定端序,就把相关字段全部显式标注。

版本

PHP 8.6 计划 2026-11-19 发布。当前稳定版还没有这些修饰符,用到 s>、l<、d> 这类写法的代码在旧版本上跑不起来。

复制全文 生成海报 PHP 二进制 网络协议 版本升级

推荐文章

程序员茄子在线接单