pack()
В этой статье рассматривается функция PHP pack(): обзор, принцип работы и примеры использования.
В этой статье рассматривается функция PHP pack(): что она делает, мини-язык форматных строк, влияние порядка байтов (endianness) на результат и практические примеры — включая обратное преобразование с помощью unpack().
Что делает pack()
pack() принимает обычные PHP-значения (целые числа, числа с плавающей точкой, строки) и записывает их побайтово в единую бинарную строку — строку, символы которой представляют собой сырые байты, а не читаемый текст.
pack(string $format, mixed ...$values): string$format— компактная форматная строка, которая описывает по порядку способ кодирования каждого значения (тип, размер и порядок байтов)....$values— одно или несколько кодируемых значений, сопоставляемых слева направо с форматными кодами.
Возвращаемое значение — бинарная строка. Поскольку эти байты обычно не являются печатаемыми, в примерах ниже результат передаётся через bin2hex(), чтобы можно было увидеть точно, какие байты были получены (два шестнадцатеричных знака = один байт).
Когда это используется?
pack() используется всякий раз, когда PHP должен работать с форматом, определённым в сырых байтах, а не в виде текста:
- Бинарные протоколы — формирование сетевых пакетов, где заголовок представляет собой «2-байтовую длину, за которой следует 4-байтовый идентификатор».
- Бинарные форматы файлов — запись чанков PNG, заголовков WAV или любого формата с полями фиксированной ширины.
- Вспомогательные хэш/крипто-операции — преобразование шестнадцатеричного дайджеста в сырую байтовую форму для передачи в
hash_hmac()илиopenssl_*. - Взаимодействие с C/встроенными системами, ожидающими поля фиксированного размера и фиксированного порядка байтов.
Для обычного PHP-хранилища serialize() или json_encode() удобнее; pack() выигрывает тогда, когда важен именно байтовый макет.
Первый пример
123 в шестнадцатеричной системе — 0x7b. Форматный код N означает «unsigned long (4 байта), сетевой порядок байтов», поэтому значение дополняется до четырёх байтов и записывается старшим байтом первым: 00 00 00 7b.
Чтение форматной строки
Форматная строка — это последовательность форматных кодов, каждый из которых при необходимости сопровождается счётчиком повторений:
N one unsigned long
N4 four unsigned longs in a row
N* as many unsigned longs as there are remaining values
A10 a 10-character space-padded stringКоды можно объединять для описания целой записи. Передаваемые значения должны соответствовать кодам по порядку:
<?php
// A 2-byte short (1) followed by a 4-byte long (16909060)
$header = pack('nN', 1, 16909060);
echo bin2hex($header); // 000101020304
?>Здесь n даёт 00 01 (short 1), а N — 01 02 03 04 (long 16909060, что равно 0x01020304). Байты следуют строго в том порядке, в котором написаны коды.
Порядок байтов (endianness)
Одно и то же число может быть сохранено с байтами в двух противоположных порядках, и pack() предоставляет отдельный код для каждого:
- Big-endian (он же сетевой порядок байтов) хранит наиболее значимый байт первым — коды
n(short) иN(long). - Little-endian хранит наименее значимый байт первым — коды
v(short) иV(long).
<?php
echo bin2hex(pack('N', 1)), "\n"; // 00000001 (big-endian)
echo bin2hex(pack('V', 1)), "\n"; // 01000000 (little-endian)
?>Это важно, потому что получатель должен использовать тот же порядок для обратного чтения данных. Сетевые протоколы стандартизированы на big-endian (N/n); многие форматы файлов (и дампы памяти x86) используют little-endian (V/v). При сомнениях для обмена данными выбирайте N/n — это и есть гарантия «сетевого порядка байтов».
Распространённые форматные коды
Наиболее часто используемые коды (полный список см. в руководстве PHP):
| Код | Значение |
|---|---|
a | Строка с NUL-дополнением |
A | Строка с дополнением пробелами |
c / C | Знаковый / беззнаковый char (1 байт) |
s / S | Знаковый / беззнаковый short, машинный порядок байтов (2 байта) |
n / N | Беззнаковый short / long, big-endian |
v / V | Беззнаковый short / long, little-endian |
f / d | Float / double, машинный формат |
H / h | Шестнадцатеричная строка, старший / младший полубайт первым |
Строковые коды используют счётчик повторений как ширину поля, а не количество значений:
<?php
echo pack('A6', 'PHP'), "|"; // PHP | (padded to 6 chars with spaces)
?>Полный цикл: pack() и unpack()
pack() записывает байты; unpack() читает их обратно в PHP-значения. Чтобы восстановить исходные данные, необходимо описать тот же макет, при этом unpack() дополнительно требует имя для каждого поля:
<?php
// Encode two fields
$binary = pack('Nn', 65536, 7);
// Decode using the same layout, naming each field
$values = unpack('Nfirst/nsecond', $binary);
echo $values['first'], ' ', $values['second']; // 65536 7
?>Слэш (/) разделяет именованные поля в формате unpack(). Если макеты на обеих сторонах не совпадают, вы получите мусор — кодирование и декодирование тесно связаны.
Подводные камни
- Коды и значения должны соответствовать. Передача меньшего числа значений, чем кодов, вызывает предупреждение и возвращает
false; лишние значения молча игнорируются (если не использовался*). - Целочисленное переполнение усекается без предупреждения.
pack('C', 300)сохраняет только младший байт (300 & 0xFF = 44) вместо того, чтобы выдать ошибку — валидируйте диапазоны самостоятельно. - Нативные коды (
s,S,i,l, числа с плавающей точкой) не переносимы. Их размер и порядок байтов зависят от платформы. Для данных, передаваемых между разными машинами, предпочитайте явные коды big-/little-endian. - Результат — бинарная строка. Не выводите её на HTML-страницу и не сравнивайте как текст; изучайте с помощью
bin2hex()или записывайте в бинарный файл/поток.
Для обратной операции перейдите к главе unpack(). Чтобы увидеть сырые байты в собственных экспериментах, незаменимым помощником, использованным на протяжении всей этой страницы, является bin2hex().
Заключение
pack() преобразует PHP-значения в точно управляемую бинарную строку с компактным форматным мини-языком для типа, размера и порядка байтов поля. Используйте её всякий раз, когда нужно создать байты, которые другая система читает по своим правилам — бинарные протоколы, форматы файлов или криптографические процедуры, — и сочетайте с unpack() для обратного чтения данных.