代码 PHP 8.6 的 pack()/unpack() 能写端序了:s</>、l</>、q</> 与 f<、d> 的用法和 ValueError 边界

2026-11-06 21:03:11

PHP 8.6 的 pack()/unpack() 能写端序了:s、l、q 与 f 的用法和 ValueError 边界

PHP 8.6 给 pack() / unpack() 的格式码加了端序修饰符:在 s、S、l、L、q、Q 之后可以写 ``(大端),f、d 同样支持。修饰符紧跟在格式码后面,位于数量和键名之前。

旧写法:移位拼高低字

以前要往报文里塞一个大端有符号 64 位整数,只能自己拆:

> 32) & 0xFFFFFFFF, $v & 0xFFFFFFFF);
}

// 新写法
$bin = pack('q>', -2);
var_dump(bin2hex($bin));            // fffffffffffffffe
var_dump(bin2hex(be64_old(-2)));    // fffffffffffffffe

移位写法依赖算术右移加掩码,负数容易写错;strrev(pack('q', $v)) 更省事,但它假设了机器是小端,换到大端机器上结果就反了。有了修饰符之后,字节序写进格式串本身,和平台无关。

支持的格式码

pack()/unpack() 沿用 Perl 语义:

码含义
s / S有符号 / 无符号 16 位
l / L有符号 / 无符号 32 位
q / Q有符号 / 无符号 64 位
f / dfloat / double
v n V N P J自带固定端序
g G e E自带固定端序的浮点

宽度是格式码定的,跟平台的 long 大小无关:l 恒为 32 位,q 恒为 64 位。这点和 C 里 long 在 LP64 / LLP64 下宽度不同要区分开。

报文字段:头长度、序号、金额

一个典型支付/硬件协议头:2 字节总长度(大端无符号)、4 字节序号(大端无符号)、8 字节金额(大端有符号,单位分,退款为负)。

Nq>', $len, $seq, $cents);
echo bin2hex($frame), PHP_EOL;
// 002400002766fffffffffffffb1e

小端对端则写成 `Slen/Nseq/q>amount', $frame);
// ['len' => 36, 'seq' => 10086, 'amount' => -1250]

// 只解尾部金额:第三个参数是起始偏移
$tail = unpack('q>amount', $frame, 6);
// ['amount' => -1250]


键名写在修饰符之后(`q>amount`)。不写键名时,返回数组的键是 1 起始的数字下标,跨版本改动字段顺序时容易错位,建议一律显式命名。偏移量按字节算,等于前面所有字段宽度之和。

## 有符号还是无符号

`q` / `Q` 只决定这 8 个字节怎么解释,PHP 侧拿到的都是 `int`,不会因为选了 `Q` 就多出一个更大的数值类型。取舍看协议本身:长度、序号一定是非负的,用 `S>` / `N`;金额、余额这类可能为负的字段用 `q>`,别用 `Q>` 然后自己判断符号位。16 位和 32 位同理,`s` 与 `S`、`l` 与 `L` 的区分只在解包时体现,打包时负值写进无符号码会按补码截断。

## 跨语言对端

Java `ByteBuffer` 默认大端,`putShort` / `putInt` / `putLong` 正好对上 `S>` / `N` / `q>`:

```java
ByteBuffer b = ByteBuffer.allocate(14);
b.putShort((short) 36).putInt(10086).putLong(-1250L);

小端对端要显式 b.order(ByteOrder.LITTLE_ENDIAN),对应 `S', 'V', 'P', 'C', 'g', 'e'] as $fmt) {
try {
pack($fmt, 1);
} catch (ValueError $e) {
echo $fmt, ': ValueError', PHP_EOL;
}
}


浮点这边 `f` 等价于 `G`、`d` 等价于 `E`,所以 `g`/`G`/`e`/`E` 上再叠修饰符属于重复指定,同样报错。已有 `g`/`G`/`e`/`E` 的代码不用改,新代码统一用 `f` 只是让格式串读起来和其他字段一致。

需要兼容 PHP 8.5 及更早版本的库,格式串里不能出现这些修饰符。
复制全文 生成海报 PHP 二进制协议 pack 接口对接

推荐文章

程序员茄子在线接单