unpack()
В этой статье рассматривается функция PHP unpack(): её синтаксис, коды форматов, порядок байтов и примеры использования.
PHP-строки — это просто последовательности сырых байтов, что делает их удобным контейнером для двоичных данных: заголовков изображений, сетевых пакетов, файловых форматов и протокольных фреймов. Функция unpack() считывает поток сырых байтов и превращает его в обычные PHP-значения (целые числа, числа с плавающей точкой, строки), с которыми можно работать. В этой статье рассматриваются сигнатура функции, её коды форматов, порядок байтов, распространённые подводные камни и совместное использование с pack().
Синтаксис
unpack(string $format, string $data, int $offset = 0): array|false| Параметр | Описание |
|---|---|
$format | Строка формата, описывающая способ интерпретации байтов (коды перечислены ниже). |
$data | Двоичная строка, из которой производится чтение. |
$offset | Позиция байта, с которой начинается чтение (добавлено в PHP 7.1). По умолчанию равно 0. |
Функция возвращает ассоциативный массив распакованных значений или false при ошибке. unpack() является обратной операцией к pack(): любой формат, записанный с помощью pack(), читается обратно с теми же кодами формата.
Первый пример
Строка формата — это последовательность из одного или нескольких кодов. Каждый код состоит из одной буквы типа данных, необязательного счётчика повторений и необязательного имени.
Здесь "C*" означает «читать каждый оставшийся байт как беззнаковое 8-битное целое». Счётчик повторений * поглощает все доступные байты. Если имя не указано, unpack() нумерует результаты начиная с 1 (не с 0):
Array
(
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)Коды форматов
Каждый код соответствует фиксированному числу байтов. Наиболее распространённые:
| Код | Тип | Размер |
|---|---|---|
C / c | Беззнаковый / знаковый char | 1 байт |
n | Беззнаковый short, big-endian | 2 байта |
v | Беззнаковый short, little-endian | 2 байта |
S / s | Беззнаковый / знаковый short, порядок машины | 2 байта |
N | Беззнаковый long, big-endian | 4 байта |
V | Беззнаковый long, little-endian | 4 байта |
L / l | Беззнаковый / знаковый long, порядок машины | 4 байта |
f / d | Float / double, порядок машины | 4 / 8 байтов |
a / A | Строка (с дополнением NUL / пробелами) | как указано |
H / h | Hex-строка, старший / младший nibble первым | на nibble |
Число после кода повторяет его (C4 читает четыре char); * читает все оставшиеся байты.
Именование полей
Реальные двоичные форматы состоят из смешанных полей, поэтому каждому обычно задают имя, а коды разделяют символом /:
"C2chars/Sint/Nlong" читает первые два байта как chars1/chars2, следующие два — как short в порядке машины int, а последние четыре — как big-endian long long:
Array
(
[chars1] => 1
[chars2] => 2
[int] => 1027
[long] => 84281096
)Когда код имеет счётчик повторений и имя, unpack() добавляет к имени индекс (chars1, chars2, …), чтобы значения не перезаписывали друг друга.
Порядок байтов важен
Одни и те же четыре байта означают разные числа в зависимости от порядка байтов. N/n — big-endian (сетевой порядок); V/v — little-endian (родной для x86); S/L следуют порядку хост-машины и поэтому непереносимы. Для данных, передаваемых между машинами — файловый формат или сетевой протокол — всегда используйте код с явным порядком байтов, чтобы результат был одинаковым везде.
<?php
$bytes = "\x01\x00\x00\x00";
print_r(unpack("Vlittle", $bytes)); // little-endian: 1
print_r(unpack("Nbig", $bytes)); // big-endian: 16777216
?>Array
(
[little] => 1
)
Array
(
[big] => 16777216
)Круговая совместимость с pack()
Поскольку unpack() — зеркальное отражение pack(), можно сериализовать значения в компактный двоичный блок и прочитать их обратно с тем же форматом:
<?php
$packed = pack("nN", 1027, 84281096); // build the bytes
$result = unpack("nshort/Nlong", $packed);
print_r($result);
?>Array
(
[short] => 1027
[long] => 84281096
)Распространённые подводные камни
- Ключи начинаются с 1. Безымянные результаты индексируются с 1, что сбивает с толку при переборе. Именуйте поля или помните об этом смещении.
- Имена со счётчиком повторений получают суффикс-индекс (
byte1,byte2), поэтомуunpack("C4byte", ...)даётbyte1…byte4, а не единственныйbyte. - Коды с порядком машины (
S,L,s,l) непереносимы. Для хранимых или передаваемых данных используйтеn/Nилиv/V. falseпри недостаточном количестве данных. Если формат требует больше байтов, чем содержится в$data,unpack()возвращаетfalseи выдаёт предупреждение — проверяйте возвращаемое значение перед использованием.
Заключение
Функция unpack() превращает сырые байты в PHP-значения с помощью компактных кодов формата и является читающей половиной пары pack(). Освойте коды порядка байтов и синтаксис именования полей — и вы сможете разобрать практически любой заголовок двоичного файла или сетевой фрейм. Для преобразования двоичных данных в читаемую hex-строку смотрите bin2hex().