perlpacktut
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ОПИСАНИЕ
- Основные принципы
- Упаковка текста
- Упаковка чисел
- Экзотические шаблоны
- Группировка шаблонов
- Длины и ширины
- Упаковка и распаковка C-структур
- Рецепты упаковки
- Раздел шуток
- Авторы
НАЗВАНИЕ
perlpacktut - руководство по pack и unpack
ОПИСАНИЕ
pack и unpack — две функции для преобразования данных в соответствии с определённым пользователем шаблоном между защищённым способом хранения значений в Perl и некоторым хорошо определённым представлением, которое может потребоваться в среде программы Perl. К сожалению, это также две из наиболее непонятых и часто упускаемых из виду функций, предоставляемых Perl. Это руководство поможет вам их понять.
Основные принципы
Большинство языков программирования не скрывают память, в которой хранятся переменные. Например, в C вы можете взять адрес некоторой переменной, и оператор sizeof укажет вам, сколько байтов выделено для переменной. Используя адрес и размер, вы можете получить доступ к хранилищу по своему усмотрению.
В Perl вы не можете произвольно обращаться к памяти, но структурное и представительное преобразование, предоставляемое pack и unpack, является отличной альтернативой. Функция pack преобразует значения в последовательность байтов, содержащую представления в соответствии с заданным спецификацией, так называемым аргументом «шаблон». unpack — обратный процесс, выводящий значения из содержимого строки байтов. (Однако будьте осторожны, поскольку не всё, что было упаковано вместе, можно аккуратно распаковать — это довольно распространённый опыт, как подтвердят опытные путешественники.)
Зачем вам может потребоваться блок памяти, содержащий некоторые значения в двоичном представлении? Одна из хороших причин — ввод и вывод, обращение к файлу, устройству или сетевому соединению, где это двоичное представление либо навязано, либо даст вам некое преимущество в обработке. Другая причина — передача данных в системный вызов, который недоступен в качестве функции Perl: syscall требует предоставления параметров, хранящихся так, как это происходит в программе на C. Даже обработка текста (как показано в следующем разделе) может быть упрощена с помощью разумного использования этих двух функций.
Чтобы увидеть, как работает (распаковка), начнём с простого кода шаблона, где преобразование происходит в медленном темпе: между содержимым последовательности байтов и строкой шестнадцатеричных цифр. Давайте воспользуемся unpack, так как это, вероятно, напомнит вам о программе просмотра дампов или о каком-то отчаянном последнем сообщении, которое неудачные программы склонны отправлять вам перед тем, как исчезнуть в бескрайних просторах.
Предполагая, что переменная $mem содержит последовательность байтов, которые мы хотели бы проверить, не делая предположений о её значении, мы можем написать
my( $hex ) = unpack( 'H*', $mem );
print "$hex\n"; После чего мы можем увидеть что-то вроде этого, где каждая пара шестнадцатеричных цифр соответствует байту:
41204d414e204120504c414e20412043414e414c2050414e414d41 Что хранилось в этом блоке памяти? Числа, символы или смесь того и другого? Предполагая, что мы работаем на компьютере, где используется кодировка ASCII (или аналогичная): шестнадцатеричные значения в диапазоне 0x40 - 0x5A обозначают заглавную букву, а 0x20 кодирует пробел. Таким образом, мы можем предположить, что это фрагмент текста, который некоторые могут прочитать, как таблоид, а другие должны будут получить доступ к таблице ASCII и пережить чувство первоклассника. Не слишком заботясь о том, как это читать, мы отмечаем, что unpack с кодом шаблона H преобразует содержимое последовательности байтов в привычную шестнадцатеричную нотацию. Поскольку «последовательность» — довольно расплывчатое указание на количество, H определена для преобразования только одной шестнадцатеричной цифры, если за ней не следует счётчик повторений. Звёздочка для счётчика повторений означает использование всего, что осталось.
Обратное преобразование — упаковка содержимого байтов из строки шестнадцатеричных цифр — выполняется также легко. Например:
my $s = pack( 'H2' x 10, 30..39 );
print "$s\n"; Поскольку мы передаём список из десяти 2-значных шестнадцатеричных строк в pack, шаблон pack должен содержать десять кодов pack. Если это выполняется на компьютере с кодировкой символов ASCII, он выведет 0123456789.
Упаковка текста
Предположим, вам нужно прочитать файл данных, подобный этому:
Date |Description | Income|Expenditure
01/24/2001 Zed's Camel Emporium 1147.99
01/28/2001 Flea spray 24.99
01/29/2001 Camel rides to tourists 235.00 Как это сделать? Вы могли бы подумать о split, но поскольку split объединяет пустые поля, вы никогда не узнаете, было ли запись доходом или расходом. Ой. Ну, вы всегда можете использовать substr:
while (<>) {
my $date = substr($_, 0, 11);
my $desc = substr($_, 12, 27);
my $income = substr($_, 40, 7);
my $expend = substr($_, 52, 7);
...
} Это не очень весело, да? На самом деле, это хуже, чем кажется; внимательный читатель может заметить, что первое поле должно иметь ширину всего 10 символов, и ошибка распространилась на все остальные числа, которые нам пришлось подсчитать вручную. Поэтому это и подвержено ошибкам, и ужасно неудобно.
Или, может быть, мы могли бы использовать регулярные выражения:
while (<>) {
my($date, $desc, $income, $expend) =
m|(\d\d/\d\d/\d{4}) (.{27}) (.{7})(.*)|;
...
} Уфф. Ну, это немного лучше, но — ну, захотите ли вы это поддерживать?
Эй, разве Perl не должен упростить подобные вещи? Ну, он делает это, если вы используете правильные инструменты. pack и unpack предназначены для того, чтобы помочь вам при работе с данными фиксированной ширины, как в приведённом выше примере. Давайте посмотрим на решение с помощью unpack:
while (<>) {
my($date, $desc, $income, $expend) = unpack("A10xA27xA7A*", $_);
...
} Это выглядит немного лучше, но нам нужно разобрать этот странный шаблон. Откуда я его взял?
Хорошо, давайте посмотрим на наши данные ещё раз; на самом деле, мы добавим заголовки и удобную линейку, чтобы отслеживать, где мы находимся.
1 2 3 4 5
1234567890123456789012345678901234567890123456789012345678
Date |Description | Income|Expenditure
01/28/2001 Flea spray 24.99
01/29/2001 Camel rides to tourists 235.00 Из этого мы можем видеть, что столбец с датой занимает от столбца 1 до столбца 10 — шириной в 10 символов. pack-ское слово для «символа» — это A, а десять из них — A10. Таким образом, если мы просто хотели извлечь даты, мы могли бы сказать так:
my($date) = unpack("A10", $_); Хорошо, что дальше? Между датой и описанием находится пустой столбец; нам нужно пропустить его. Шаблон x означает «перейти вперёд», поэтому нам нужен один из них. Далее у нас есть ещё группа символов с 12 по 38. Это ещё 27 символов, следовательно, A27. (Не допускайте ошибки «столба» — между 12 и 38 находится 27 символов, а не 26. Посчитайте их!)
Теперь мы пропускаем ещё один символ и берём следующие 7 символов:
my($date,$description,$income) = unpack("A10xA27xA7", $_); Теперь самое умное. Строки в нашем журнале, которые являются только доходами, а не расходами, могут заканчиваться в столбце 46. Поэтому мы не хотим говорить нашему шаблону unpack, что нам нужно найти ещё 12 символов; мы просто скажем «если что-то осталось, возьмите это». Как вы, вероятно, догадались из регулярных выражений, это означает *: «используйте всё, что осталось».
-
Однако будьте осторожны, в отличие от регулярных выражений, если шаблон
unpackне соответствует входным данным, Perl закричит и умрёт.
Поэтому, объединив всё вместе:
my ($date, $description, $income, $expend) =
unpack("A10xA27xA7xA*", $_); Теперь наши данные обработаны. Думаю, теперь мы хотим подсчитать наши доходы и расходы и добавить в конец нашего журнала ещё одну строку — в том же формате — с указанием того, сколько мы заработали и сколько потратили:
while (<>) {
my ($date, $desc, $income, $expend) =
unpack("A10xA27xA7xA*", $_);
$tot_income += $income;
$tot_expend += $expend;
}
$tot_income = sprintf("%.2f", $tot_income); # Get them into
$tot_expend = sprintf("%.2f", $tot_expend); # "financial" format
$date = POSIX::strftime("%m/%d/%Y", localtime);
# OK, let's go:
print pack("A10xA27xA7xA*", $date, "Totals",
$tot_income, $tot_expend); О, хмм. Это не сработало. Посмотрим, что произошло:
01/24/2001 Zed's Camel Emporium 1147.99
01/28/2001 Flea spray 24.99
01/29/2001 Camel rides to tourists 1235.00
03/23/2001Totals 1235.001172.98 Хорошо, это начало, но что случилось с пробелами? Мы указали x, не так ли? Разве это не должно пропустить пробелы? Давайте посмотрим, что говорит "pack" в perlfunc:
x A null byte. Уфф. Неудивительно. Существует большая разница между «нулевым байтом», символом ноль и «пробелом», символом 32. Perl поместил что-то между датой и описанием, но, к сожалению, мы не можем его увидеть!
На самом деле нам нужно расширить ширину полей. Шаблон A заполняет отсутствующие символы пробелами, поэтому мы можем использовать дополнительные пробелы для выравнивания наших полей, как в этом примере:
print pack("A11 A28 A8 A*", $date, "Totals",
$tot_income, $tot_expend); (Обратите внимание, что вы можете вставлять пробелы в шаблон для лучшей читаемости, но они не преобразуются в пробелы в выводе.) Вот что мы получили на этот раз:
01/24/2001 Zed's Camel Emporium 1147.99
01/28/2001 Flea spray 24.99
01/29/2001 Camel rides to tourists 1235.00
03/23/2001 Totals 1235.00 1172.98 Это немного лучше, но у нас всё ещё есть последний столбец, который нужно сместить дальше. Есть простой способ исправить это: к сожалению, мы не можем заставить pack выравнивать наши поля по правому краю, но мы можем заставить sprintf сделать это:
$tot_income = sprintf("%.2f", $tot_income);
$tot_expend = sprintf("%12.2f", $tot_expend);
$date = POSIX::strftime("%m/%d/%Y", localtime);
print pack("A11 A28 A8 A*", $date, "Totals",
$tot_income, $tot_expend); На этот раз мы получили правильный ответ:
01/28/2001 Flea spray 24.99
01/29/2001 Camel rides to tourists 1235.00
03/23/2001 Totals 1235.00 1172.98 Итак, вот как мы потребляем и генерируем данные фиксированной ширины. Давайте подведём итоги того, что мы видели в pack и unpack до сих пор:
-
Используйте
packдля перехода от нескольких фрагментов данных к одному формату фиксированной ширины; используйтеunpack, чтобы преобразовать строку в формате фиксированной ширины в несколько фрагментов данных. -
Формат упаковки
Aозначает "любой символ"; если выpackи у вас закончились данные для упаковки,packзаполнит оставшееся место пробелами. -
xозначает "пропустить байт" приunpack; приpack, это означает "вставить нулевой байт" - это, вероятно, не то, что вы имеете в виду, если работаете с обычным текстом. -
Вы можете следовать за форматами с числами, чтобы указать, сколько символов должно быть затронуто этим форматом:
A12означает "взять 12 символов";x6означает "пропустить 6 байтов" или "символ 0, 6 раз". -
Вместо числа вы можете использовать
*, чтобы означать "использовать все оставшиеся данные".Предупреждение: при упаковке нескольких фрагментов данных,
*означает только "использовать весь текущий фрагмент данных". То естьpack("A*A*", $one, $two)упаковывает все
$oneв первыйA*, а все$twoво второй. Это общий принцип: каждый символ формата соответствует одному фрагменту данных, который нужноpack.
Упаковывание чисел
Достаточно о текстовых данных. Перейдем к важному, что pack и unpack делают лучше всего: обработке двоичных форматов для чисел. Конечно, существует не один двоичный формат – жизнь была бы слишком простой – но Perl выполнит всю кропотливую работу за вас.
Целые числа
Упаковывание и распаковывание чисел подразумевает преобразование в и из некоторого конкретного двоичного представления. Отложим на время числа с плавающей точкой, основные свойства любого такого представления таковы:
-
количество байтов, используемых для хранения целого числа,
-
является ли содержимое знаковым или беззнаковым числом,
-
порядок байтов: является ли первый байт младшим или старшим байтом (соответственно, little-endian или big-endian).
Например, чтобы упаковать 20302 в знаковое 16-битное целое число в представлении вашей системы, вы напишите
my $ps = pack( 's', 20302 ); Результат, опять же, строка, теперь содержащая 2 байта. Если вы распечатаете эту строку (чего, как правило, не рекомендуется делать), вы можете увидеть ON или NO (в зависимости от порядка байтов вашей системы) – или что-то совершенно другое, если ваша система не использует кодировку символов ASCII. Распаковка $ps с той же шаблоном возвращает исходное значение целого числа:
my( $s ) = unpack( 's', $ps ); Это верно для всех числовых кодов шаблона. Но не ждите чудес: если упакованное значение превышает выделенную емкость байтов, старшие биты будут молча отброшены, а unpack, конечно, не сможет извлечь их из какого-то волшебного шляпы. И, когда вы упаковываете с использованием знакового кода шаблона, такого как s, избыточное значение может привести к установке бита знака, и при распаковке это разумно вернёт отрицательное значение.
16 бит не позволят вам далеко продвинуться с целыми числами, но существуют l и L для знакомых и беззнаковых 32-битных целых чисел. А если этого недостаточно и ваша система поддерживает 64-битные целые числа, вы можете продвинуться гораздо ближе к бесконечности с кодами упаковки q и Q. Заметным исключением являются коды упаковки i и I для знакомых и беззнаковых целых чисел "локального пользовательского" типа: такое целое число займет столько байтов, сколько возвращает локальный компилятор C для sizeof(int), но оно будет использовать по меньшей мере 32 бита.
Каждый из кодов упаковки целых чисел sSlLqQ приводит к фиксированному числу байтов, независимо от того, где вы выполняете свою программу. Это может быть полезно для некоторых применений, но это не обеспечивает портативный способ передачи структур данных между Perl и программами C (что неизбежно при вызове расширений XS или функции Perl syscall), или при чтении или записи двоичных файлов. В этом случае вам понадобятся коды шаблонов, которые зависят от того, что ваш локальный компилятор C компилирует, когда вы пишете short или unsigned long, например. Эти коды и соответствующие длины в байтах показаны в таблице ниже. Поскольку стандарт C предоставляет значительную свободу в отношении относительных размеров этих типов данных, фактические значения могут различаться, и поэтому значения представлены в виде выражений на C и Perl. (Если вы хотите использовать значения из %Config в вашей программе, вы должны импортировать их с помощью use Config.)
signed unsigned byte length in C byte length in Perl
s! S! sizeof(short) $Config{shortsize}
i! I! sizeof(int) $Config{intsize}
l! L! sizeof(long) $Config{longsize}
q! Q! sizeof(long long) $Config{longlongsize} Коды i! и I! ничем не отличаются от i и I; они допускаются для полноты.
Распаковка кадра стека
Запрос конкретного порядка байтов может потребоваться, когда вы работаете с двоичными данными, полученными из определенной архитектуры, в то время как ваша программа может выполняться на совершенно другой системе. Например, предположим, что у вас есть 24 байта, содержащие кадр стека, как это происходит на Intel 8086:
+---------+ +----+----+ +---------+
TOS: | IP | TOS+4:| FL | FH | FLAGS TOS+14:| SI |
+---------+ +----+----+ +---------+
| CS | | AL | AH | AX | DI |
+---------+ +----+----+ +---------+
| BL | BH | BX | BP |
+----+----+ +---------+
| CL | CH | CX | DS |
+----+----+ +---------+
| DL | DH | DX | ES |
+----+----+ +---------+ Во-первых, отметим, что эта проверенная временем 16-битная процессор использует порядок little-endian, и поэтому младший байт хранится по меньшему адресу. Для распаковки такого (беззнакового) короткого целого числа нам потребуется код v. Повторный счёт распаковывает все 12 коротких чисел:
my( $ip, $cs, $flags, $ax, $bx, $cd, $dx, $si, $di, $bp, $ds, $es ) =
unpack( 'v12', $frame ); В качестве альтернативы, мы могли бы использовать C, чтобы распаковать отдельные доступные байтовые регистры FL, FH, AL, AH и т. д.:
my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
unpack( 'C10', substr( $frame, 4, 10 ) ); Было бы неплохо сделать это за один раз: распаковать короткое целое число, вернуться назад немного, а затем распаковать 2 байта. Поскольку Perl хороший, он предоставляет код шаблона X для возврата назад на один байт. Объединив все это, мы можем написать:
my( $ip, $cs,
$flags,$fl,$fh,
$ax,$al,$ah, $bx,$bl,$bh, $cx,$cl,$ch, $dx,$dl,$dh,
$si, $di, $bp, $ds, $es ) =
unpack( 'v2' . ('vXXCC' x 5) . 'v5', $frame ); (Неуклюжее построение шаблона можно избежать – просто продолжайте читать!)
Мы постарались построить шаблон так, чтобы он соответствовал содержимому нашего буфера кадра. В противном случае мы либо получим неопределённые значения, либо unpack не сможет распаковать всё. Если pack исчерпает элементы, он будет обеспечивать пустые строки (которые преобразуются в нули всякий раз, когда код упаковки так говорит).
Как есть яйцо на сети
Код упаковки для big-endian (старший байт по наименьшему адресу) – n для 16-битных и N для 32-битных целых чисел. Вы используете эти коды, если знаете, что ваши данные поступают от совместимой архитектуры, но, удивительно, вы также должны использовать эти коды упаковки, если обмениваетесь двоичными данными через сеть с системой, о которой вы практически ничего не знаете. Причина проста: этот порядок был выбран в качестве сетевого порядка, и все уважающие стандарт программы должны следовать этой конвенции. (Конечно, это серьёзная поддержка одной из лиллипутийских партий и может повлиять на политическое развитие там.) Таким образом, если протокол ожидает, что вы отправите сообщение, отправив сначала длину, а затем столько байтов, вы можете написать:
my $buf = pack( 'N', length( $msg ) ) . $msg; или даже:
my $buf = pack( 'NA*', length( $msg ), $msg ); и передать $buf в вашу процедуру отправки. Некоторые протоколы требуют, чтобы счёт включал длину самого счёта: тогда просто добавьте 4 к длине данных. (Но убедитесь, что вы прочитали "Длины и ширины" перед реальным кодированием!)
Модификаторы порядка байтов
В предыдущих разделах мы узнали, как использовать n, N, v и V для упаковки и распаковки целых чисел с big- или little-endian порядком байтов. Хотя это неплохо, это всё ещё довольно ограничено, потому что оно опускает все виды знакомых целых чисел, а также 64-битные целые числа. Например, если вы захотите распаковать последовательность знакомых целых чисел big-endian по 16 бит независимо от платформы, вам придётся написать:
my @data = unpack 's*', pack 'S*', unpack 'n*', $buf; Это ужасно. Начиная с Perl 5.9.2, есть гораздо лучший способ выразить своё желание определённого порядка байтов: модификаторы > и <. > – это модификатор big-endian, а < – little-endian. Используя их, мы можем переписать приведенный выше код как:
my @data = unpack 's>*', $buf; Как вы можете видеть, «большой конец» стрелки касается s, что является удобным способом запомнить, что > – это модификатор big-endian. То же самое, очевидно, работает для <, где «малый конец» касается кода.
Вы, вероятно, найдёте эти модификаторы ещё более полезными, если вам придётся иметь дело со структурами C big- или little-endian. Обязательно прочтите "Упаковка и распаковка структур C" для получения дополнительной информации об этом.
Числа с плавающей точкой
Для упаковки чисел с плавающей точкой у вас есть выбор между кодами упаковки f, d, F и D. f и d упаковывают в (или распаковывают из) одинарной или двойной точности, как это предоставляет ваша система. Если ваша система поддерживает это, D можно использовать для упаковки и распаковки (long double) значений, которые могут предложить даже большую точность, чем f или d. Обратите внимание, что существуют разные форматы long double.
F упаковывает NV, что является типом с плавающей точкой, используемым Perl внутри.
Нет такого понятия, как сетевое представление для вещественных чисел, поэтому, если вы хотите отправить свои вещественные числа через компьютерные границы, лучше придерживаться текстового представления, возможно, используя шестнадцатеричный формат вещественных чисел (избегая потери при десятичном преобразовании), если вы абсолютно уверены, что находится на другом конце линии. Для более смелых вы можете использовать модификаторы порядка байтов из предыдущего раздела также с кодами чисел с плавающей точкой.
Экзотические шаблоны
Битовые строки
Биты – это атомы в мире памяти. Доступ к отдельным битам может потребоваться либо как крайняя мера, либо потому, что это наиболее удобный способ обработки ваших данных. Битовая (распаковка) преобразует строки, содержащие последовательность 0 и 1 символов, в последовательность байтов, каждый из которых содержит группу из 8 бит. Это почти так же просто, как звучит, за исключением того, что есть два способа записать содержимое байта в виде битовой строки. Давайте рассмотрим помеченный байт:
7 6 5 4 3 2 1 0
+-----------------+
| 1 0 0 0 1 1 0 0 |
+-----------------+
MSB LSB Это всё та же история с яйцами: некоторые считают, что как битовую строку это должно быть записано как «10001100», то есть, начиная со старшего бита, другие настаивают на «00110001». Ну, Perl не предвзято, поэтому у нас есть два кода битовых строк:
$byte = pack( 'B8', '10001100' ); # start with MSB
$byte = pack( 'b8', '00110001' ); # start with LSB Невозможно упаковать или распаковать битовые поля — только целые байты. pack всегда начинается с следующей границе байта и «округляется» до следующей кратной 8, добавляя нулевые биты по мере необходимости. (Если вам нужны битовые поля, есть "vec" в perlfunc. Или вы можете реализовать обработку битовых полей на уровне строковых символов, используя split, substr и конкатенацию на распакованных битовых строках.)
Чтобы проиллюстрировать распаковку битовых строк, мы разложим простой регистр состояния (символ «-» обозначает «зарезервированный» бит):
+-----------------+-----------------+
| S Z - A - P - C | - - - - O D I T |
+-----------------+-----------------+
MSB LSB MSB LSB Преобразование этих двух байтов в строку можно выполнить с помощью шаблона распаковки 'b16'. Для получения отдельных значений битов из битовой строки мы используем split со шаблоном разделителя «пусто», который разделяет строку на отдельные символы. Значения битов из «зарезервированных» позиций просто присваиваются undef, удобная запись для «мне все равно, куда это пойдет».
($carry, undef, $parity, undef, $auxcarry, undef, $zero, $sign,
$trace, $interrupt, $direction, $overflow) =
split( //, unpack( 'b16', $status ) ); Мы могли бы использовать шаблон распаковки 'b12' также, так как последние 4 бита можно игнорировать.
Кодирование UUencode
Еще одним нестандартным элементом в алфавите шаблонов является u, который упаковывает «uuencoded строку». («uu» — это сокращение от Unix-to-Unix.) Вероятность того, что вам когда-либо понадобится эта кодировка, невелика, она была изобретена для преодоления недостатков устаревших сред передачи данных, которые не поддерживают ничего кроме простого ASCII-данных. Основной рецепт прост: взять три байта или 24 бита. Разделить их на 4 шестибитовых пакета, добавив пробел (0x20) к каждому. Повторять, пока все данные не будут объединены. Сложить группы из 4 байт в строки длиной не более 60 и прикрепить к ним впереди количество исходных байтов (увеличенных на 0x20) и "\n" в конце. — pack повар подготовит это для вас, по заказу, когда вы выберете код pack u из меню:
my $uubuf = pack( 'u', $bindat ); Счётчик повторений после u устанавливает количество байтов для помещения в строку с uuencoded данными, по умолчанию максимум 45, но может быть установлен на какое-либо (меньшее) целое кратное трём. unpack просто игнорирует счётчик повторений.
Вычисление сумм
Еще более странный код шаблона — %<число>. Во-первых, потому что он используется как префикс к какому-либо другому коду шаблона. Во-вторых, потому что он не может быть использован в pack вообще, а в-третьих, в unpack не возвращает данные, определённые кодом шаблона, которому предшествует. Вместо этого он вернёт целое число в число бит, вычисленное из значения данных путём суммирования. Для числовых кодов распаковки особых достижений не наблюдается:
my $buf = pack( 'iii', 100, 20, 3 );
print unpack( '%32i3', $buf ), "\n"; # prints 123 Для строковых значений % возвращает сумму значений байтов, избавляя вас от необходимости цикла суммирования с substr и ord:
print unpack( '%32A*', "\x01\x10" ), "\n"; # prints 17 Хотя код % задокументирован как возвращающий «контрольную сумму», не доверяйте таким значениям! Даже при применении к небольшому количеству байтов они не гарантируют заметное расстояние Хэмминга.
В связи с b или B, % просто добавляет биты, и это может быть использовано для эффективного подсчета установленных битов:
my $bitcount = unpack( '%32b*', $mask ); И бит чётности можно определить следующим образом:
my $evenparity = unpack( '%1b*', $mask ); Unicode
Unicode — это набор символов, который может представлять большинство символов большинства языков мира, предоставляя место более чем для миллиона различных символов. Unicode 3.1 определяет 94 140 символов: Символы базовой латиницы назначены числам 0—127. Следующий диапазон, до 255, включает дополнение латинской-1 с символами, используемыми на нескольких европейских языках. После некоторых дополнительных расширений латинских символов мы находим наборы символов языков, использующих нелатинские алфавиты, перемежающиеся с различными наборами символов, такими как валютные символы, Zapf Dingbats или шрифт Брайля. (Возможно, вы захотите посетить http://www.unicode.org/, чтобы взглянуть на некоторые из них — мои личные фавориты — телугу и каннада.)
Набор символов Unicode сопоставляет символы с целыми числами. Кодирование этих чисел в равном количестве байтов более чем удвоило бы требования к хранению текстов, написанных латинскими алфавитами. Кодировка UTF-8 избегает этого, храня наиболее распространённые (с западной точки зрения) символы в одном байте, а редкие — в трёх и более байтах.
Perl использует UTF-8 в своей внутренней работе для большинства строковых представлений Unicode.
Так что же это имеет общего с pack? Ну, если вы хотите создать строку Unicode (которая в итоге закодирована как UTF-8), вы можете сделать это, используя шаблон кодирования U. В качестве примера, давайте создадим символ евро (€ (код 0x20AC)):
$UTF8{Euro} = pack( 'U', 0x20AC );
# Equivalent to: $UTF8{Euro} = "\x{20ac}"; Просмотрев $UTF8{Euro}, видно, что он содержит 3 байта: «\xe2\x82\xac». Однако он содержит только 1 символ с кодом 0x20AC. Обратный переход можно завершить с помощью unpack:
$Unicode{Euro} = unpack( 'U', $UTF8{Euro} ); Распаковка с помощью шаблона кода U также работает со строками байтов, закодированных в UTF-8.
Обычно вы захотите упаковать или распаковать строки UTF-8:
# pack and unpack the Hebrew alphabet
my $alefbet = pack( 'U*', 0x05d0..0x05ea );
my @hebrew = unpack( 'U*', $utf ); Обратите внимание: в общем случае вы лучше используете Encode::decode('UTF-8', $utf) для декодирования строки байтов UTF-8 в строку Unicode Perl, и Encode::encode('UTF-8', $str) для кодирования строки Unicode Perl в байты UTF-8. Эти функции предоставляют средства для обработки недопустимых последовательностей байтов и обычно имеют более удобный интерфейс.
Другая портативная бинарная кодировка
Код упаковки w был добавлен для поддержки портативной схемы бинарного кодирования данных, которая выходит за рамки простых целых чисел. (Подробности можно найти на http://Casbah.org/, проект Scarab.) Сжатое целое беззнаковое число BER хранит цифры по основанию 128, начиная с самой значимой цифры, с наименьшим количеством цифр, как только возможно. Восьмой бит (старший бит) устанавливается в каждом байте, за исключением последнего. Предела для кодирования BER нет, но Perl не будет доводить его до крайности.
my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 ); Шестнадцатеричный вывод $berbuf со вставленными пробелами в нужных местах показывает 01 8100 8101 81807F. Так как последний байт всегда меньше 128, unpack знает, где остановиться.
Группирование шаблонов
Перед Perl 5.8 повторения шаблонов приходилось делать путём x-умножения строк шаблонов. Теперь есть лучший способ, так как мы можем использовать коды упаковки ( и ) в сочетании со счётчиком повторений. Шаблон unpack из примера стека фрейма можно записать так:
unpack( 'v2 (vXXCC)5 v5', $frame ) Давайте исследуем эту возможность подробнее. Начнём с эквивалента
join( '', map( substr( $_, 0, 1 ), @str ) ) который возвращает строку, состоящую из первого символа каждой строки. Используя pack, мы можем написать
pack( '(A)'.@str, @str ) или, поскольку счётчик повторений * означает «повторять столько раз, сколько нужно», просто
pack( '(A)*', @str ) (Обратите внимание, что шаблон A* упаковал бы $str[0] в полную длину.)
Чтобы упаковать даты, хранящиеся как тройки (день, месяц, год) в массиве @dates в последовательность байт, байт, короткое целое число, мы можем написать
$pd = pack( '(CCS)*', map( @$_, @dates ) ); Чтобы поменять местами пары символов в строке (с чётной длиной), можно использовать несколько методов. Во-первых, давайте используем x и X для перехода вперёд и назад:
$s = pack( '(A)*', unpack( '(xAXXAx)*', $s ) ); Мы также можем использовать @ для перехода к смещению, где 0 — позиция, в которой мы находились, когда последний ( был встречен:
$s = pack( '(A)*', unpack( '(@1A @0A @2)*', $s ) ); Наконец, есть и совершенно другой подход, заключающийся в распаковке коротких целых чисел большого порядка и упаковке их в обратном порядке байтов:
$s = pack( '(v)*', unpack( '(n)*', $s ); Длины и ширины
Длины строк
В предыдущем разделе мы рассмотрели сетевое сообщение, которое было создано путём добавления длины двоичного сообщения к самому сообщению. Вы обнаружите, что упаковка длины, за которой следуют столько байтов данных, является часто используемым рецептом, так как добавление нулевого байта не сработает, если нулевой байт может быть частью данных. Вот пример, где оба подхода используются: после двух строк с нулевым завершением с исходным и целевым адресом отправляется короткое сообщение (мобильному телефону) после байта длины:
my $msg = pack( 'Z*Z*CA*', $src, $dst, length( $sm ), $sm ); Распаковать это сообщение можно с помощью того же шаблона:
( $src, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg ); Существует скрытая ловушка: добавление другого поля после короткого сообщения (в переменной $sm) — это нормально при упаковке, но это нельзя распаковать наивно:
# pack a message
my $msg = pack( 'Z*Z*CA*C', $src, $dst, length( $sm ), $sm, $prio );
# unpack fails - $prio remains undefined!
( $src, $dst, $len, $sm, $prio ) = unpack( 'Z*Z*CA*C', $msg ); Код pack A* поглощает все оставшиеся байты, и $prio остаётся неопределённым! Прежде чем разочарование подорвёт моральный дух: Perl имеет козырь в рукаве, чтобы справиться и с этим трюком. Смотрите:
# pack a message: ASCIIZ, ASCIIZ, length/string, byte
my $msg = pack( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );
# unpack
( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg ); Объединение двух кодов pack с косой чертой (/) связывает их с одним значением из списка аргументов. В pack берётся длина аргумента и упаковывается по первому коду, а сам аргумент добавляется после преобразования с помощью шаблона кода после косой черты. Это избавляет нас от необходимости вставки вызова length, но именно в unpack мы действительно достигаем успеха: значение байта длины отмечает конец строки, которая должна быть взята из буфера. Поскольку это сочетание имеет смысл только тогда, когда второй код pack не является a*, A* или Z*, Perl не позволит вам этого.
Код pack, предшествующий /, может быть любым, подходящим для представления числа: все числовые двоичные коды pack, а также текстовые коды, такие как A4 или Z*:
# pack/unpack a string preceded by its length in ASCII
my $buf = pack( 'A4/A*', "Humpty-Dumpty" );
# unpack $buf: '13 Humpty-Dumpty'
my $txt = unpack( 'A4/A*', $buf ); / не реализован в Perl до версии 5.6, поэтому, если ваш код должен работать в старых версиях Perl, вам нужно будет unpack( 'Z* Z* C'), чтобы получить длину, а затем использовать её для создания новой строки unpack. Например
# pack a message: ASCIIZ, ASCIIZ, length, string, byte
# (5.005 compatible)
my $msg = pack( 'Z* Z* C A* C', $src, $dst, length $sm, $sm, $prio );
# unpack
( undef, undef, $len) = unpack( 'Z* Z* C', $msg );
($src, $dst, $sm, $prio) = unpack ( "Z* Z* x A$len C", $msg ); Но второй unpack идёт впереди. Он не использует простую строку-литерал в качестве шаблона. Так что, может быть, нам следует ввести...
Динамические шаблоны
До сих пор мы видели литералы, используемые в качестве шаблонов. Если у списка элементов pack нет фиксированной длины, требуется выражение, строящее шаблон (когда-либо, по какой-либо причине, ()* нельзя использовать). Вот пример: чтобы сохранить именованные строковые значения таким образом, чтобы их удобно было проанализировать программе C, мы создаём последовательность имён и ASCII-строк с нулевым окончанием, с = между именем и значением, а также с дополнительным нулевым разделителем. Вот как:
my $env = pack( '(A*A*Z*)' . keys( %Env ) . 'C',
map( { ( $_, '=', $Env{$_} ) } keys( %Env ) ), 0 ); Давайте рассмотрим шестеренки этого байтового мельницы по одной. Вот вызов map, создающий элементы, которые мы намереваемся поместить в буфер $env: к каждому ключу (в $_) он добавляет разделитель = и значение записи хеша. Каждый тройка упаковывается с кодом шаблона A*A*Z*, который повторяется в соответствии с количеством ключей. (Да, именно это возвращает функция keys в скалярном контексте.) Чтобы получить последний нулевой байт, мы добавляем 0 в конце списка pack, который будет упакован с помощью C. (Внимательные читатели могли заметить, что мы могли бы опустить 0.)
Для обратной операции нам необходимо определить количество элементов в буфере, прежде чем мы позволим unpack его разобрать:
my $n = $env =~ tr/\0// - 1;
my %env = map( split( /=/, $_ ), unpack( "(Z*)$n", $env ) ); Функция tr подсчитывает нулевые байты. Вызов unpack возвращает список пар имя-значение, каждая из которых разбирается в блоке map.
Подсчет Повторений
Вместо хранения стоп-символа в конце элемента данных (или списка элементов), мы могли бы поместить счетчик перед данными. Опять же, мы упаковываем ключи и значения хеша, предваряя каждый из них счетчиком длины без знака, а в самом начале мы храним количество пар:
my $env = pack( 'S(S/A* S/A*)*', scalar keys( %Env ), %Env ); Это упрощает обратную операцию, так как количество повторений можно распаковать с помощью кода /:
my %env = unpack( 'S/(S/A* S/A*)', $env ); Обратите внимание, что это один из редких случаев, когда вы не можете использовать один и тот же шаблон для pack и unpack, потому что pack не может определить счетчик повторений для группы ().
Intel HEX
Intel HEX — это формат файла для представления двоичных данных, в основном для программирования различных микросхем, в виде текстового файла. (См. http://en.wikipedia.org/wiki/.hex для подробного описания и http://en.wikipedia.org/wiki/SREC_(file_format) для формата Motorola S-record, который можно разобрать с помощью той же техники.) Каждая строка начинается с двоеточия (':') и за ним следует последовательность шестнадцатеричных символов, определяющих количество байтов n (8 бит), адрес (16 бит, big endian), тип записи (8 бит), n байтов данных и контрольную сумму (8 бит), вычисляемую как младший байт дополнительного кода к двум сумме предыдущих байтов. Пример: :0300300002337A1E.
Первый шаг обработки такой строки — преобразование шестнадцатеричных данных в двоичный вид, чтобы получить четыре поля, при этом проверяя контрольную сумму. Здесь ничего удивительного: мы начнем с простого вызова pack для преобразования всего в двоичный вид:
my $binrec = pack( 'H*', substr( $hexrec, 1 ) ); Полученная последовательность байтов удобнее всего для проверки контрольной суммы. Не замедляйте свою программу циклом for, суммируя значения ord байтов этой строки — код unpack % — это то, что нужно использовать для вычисления 8-битной суммы всех байтов, которая должна быть равна нулю:
die unless unpack( "%8C*", $binrec ) == 0; Наконец, давайте получим эти четыре поля. К этому моменту у вас не должно возникнуть проблем с первыми тремя полями — но как мы можем использовать количество байтов данных в первом поле в качестве длины для поля данных? Здесь на помощь приходят коды x и X, так как они позволяют перепрыгивать вперед и назад в строке для распаковки.
my( $addr, $type, $data ) = unpack( "x n C X4 C x3 /a", $bin ); Код x пропускает байт, так как нам пока не нужно количество. Код n обрабатывает 16-битное целое число с порядком байтов big-endian, а C распаковывает тип записи. Находясь в смещении 4, где начинаются данные, нам нужно количество. X4 возвращает нас к исходному положению — байту со смещением 0. Теперь мы выбираем счетчик и перемещаемся к смещению 4, где мы полностью готовы извлечь точное количество байтов данных, оставив последний байт контрольной суммы в покое.
Упаковки и распаковки C-структур
В предыдущих разделах мы увидели, как упаковывать числа и строковые данные. Если бы не было пары проблем, мы могли бы закончить этот раздел кратким замечанием, что структуры C не содержат ничего другого, и поэтому вы уже знаете все, что нужно знать. К сожалению, нет: читайте дальше, пожалуйста.
Если вам нужно иметь дело с большим количеством структур C и вы не хотите вручную править все ваши шаблоны строк, вам, вероятно, захочется взглянуть на модуль CPAN Convert::Binary::C. Он не только может напрямую анализировать ваш исходный код C, но также имеет встроенную поддержку всех мелочей, описанных далее в этом разделе.
Яма Выравнивания
При рассмотрении скорости по сравнению с требованиями к памяти баланс был смещен в пользу более быстрого выполнения. Это повлияло на способ, которым C-компиляторы распределяют память для структур: на архитектурах, где 16- или 32-битное операнд может перемещаться быстрее между местами в памяти или в/из регистров процессора, если оно выровнено по четному или кратному 4 или даже кратному 8 адресу, C-компилятор предоставит вам это преимущество скорости, поместив дополнительные байты в структуры. Если вы не пересекаете побережье C, это вряд ли причинит вам неприятности (хотя вы должны беспокоиться, когда проектируете большие структуры данных или хотите, чтобы ваш код был переносимым между архитектурами (вы этого хотите, не так ли?)).
Чтобы увидеть, как это влияет на pack и unpack, мы сравним эти две структуры C:
typedef struct {
char c1;
short s;
char c2;
long l;
} gappy_t;
typedef struct {
long l;
short s;
char c1;
char c2;
} dense_t; Как правило, C-компилятор выделяет 12 байтов для переменной gappy_t, но требует только 8 байтов для переменной dense_t. После дальнейшего изучения этого вопроса, мы можем создать карты памяти, показывающие, где скрыты дополнительные 4 байта:
0 +4 +8 +12
+--+--+--+--+--+--+--+--+--+--+--+--+
|c1|xx| s |c2|xx|xx|xx| l | xx = fill byte
+--+--+--+--+--+--+--+--+--+--+--+--+
gappy_t
0 +4 +8
+--+--+--+--+--+--+--+--+
| l | h |c1|c2|
+--+--+--+--+--+--+--+--+
dense_t И вот где возникает первая особенность: шаблоны pack и unpack должны быть заполнены кодами x, чтобы получить эти дополнительные байты заполнения.
Естественный вопрос: «Почему Perl не может компенсировать пробелы?» требует ответа. Одна из хороших причин заключается в том, что C-компиляторы могут предоставлять расширения (не ANSI), позволяющие контролировать способ выравнивания структур, даже на уровне отдельных полей структуры. И, если этого было недостаточно, есть коварная вещь под названием union, где количество байтов заполнения нельзя вывести только из выравнивания следующего элемента.
Хорошо, давайте приступим. Вот один из способов правильно выровнять структуры, вставив коди шаблоны x, которые не берут соответствующий элемент из списка:
my $gappy = pack( 'cxs cxxx l!', $c1, $s, $c2, $l ); Обратите внимание на ! после l: Мы хотим убедиться, что мы упаковываем целое число типа long так, как его компилирует наш C-компилятор. И даже сейчас, это будет работать только для платформ, где компилятор выравнивает вещи, как указано выше. И где-то есть платформа, где это не работает. [Вероятно, Cray, где short, int и long все имеют по 8 байтов. :-)]
Подсчет байтов и наблюдение за выравниванием в длинных структурах неизбежно приведут к задержке. Разве нет способа создать шаблон с помощью простой программы? Вот программа на C, которая выполняет эту задачу:
#include <stdio.h>
#include <stddef.h>
typedef struct {
char fc1;
short fs;
char fc2;
long fl;
} gappy_t;
#define Pt(struct,field,tchar) \
printf( "@%d%s ", offsetof(struct,field), # tchar );
int main() {
Pt( gappy_t, fc1, c );
Pt( gappy_t, fs, s! );
Pt( gappy_t, fc2, c );
Pt( gappy_t, fl, l! );
printf( "\n" );
} Выходная строка может быть использована в качестве шаблона в вызове pack или unpack:
my $gappy = pack( '@0c @2s! @4c @8l!', $c1, $s, $c2, $l ); Ого, еще один код шаблона — как будто у нас не было достаточно. Но @ спасает нас, позволяя указать смещение от начала буфера упаковки до следующего элемента: это просто значение макроса offsetof (определенного в <stddef.h>), возвращаемого при указании типа struct и имени одного из его полей («имя члена» в терминологии стандарта C).
Ни использование смещений, ни добавление x для заполнения пробелов не является удовлетворительным. (Просто представьте, что произойдет, если структура изменится.) Нам действительно нужен способ сказать «пропустить необходимое количество байтов до следующего кратного N». На языке шаблонов это говорится с помощью x!N, где N заменяется соответствующим значением. Вот следующая версия упаковки нашей структуры:
my $gappy = pack( 'c x!2 s c x!4 l!', $c1, $s, $c2, $l ); Это определенно лучше, но нам все еще нужно знать длину всех целых чисел, а переносимость находится далеко. Вместо 2, например, мы хотим сказать «любую длину, которая имеет тип short». Но это можно сделать, заключив соответствующий код упаковки в скобки: [s]. Итак, вот самое лучшее, что мы можем сделать:
my $gappy = pack( 'c x![s] s c x![l!] l!', $c1, $s, $c2, $l ); Работа с порядком байтов
Теперь представьте, что мы хотим упаковать данные для машины с другим порядком байтов. Сначала нам нужно выяснить, какой размер имеют типы данных на целевой машине. Предположим, что long — 32 бита, а short — 16 бит. Затем вы можете переписать шаблон как:
my $gappy = pack( 'c x![s] s c x![l] l', $c1, $s, $c2, $l ); Если целевая машина имеет порядок байтов little-endian, мы можем написать:
my $gappy = pack( 'c x![s] s< c x![l] l<', $c1, $s, $c2, $l ); Это принудительно устанавливает короткий и длинный члены в little-endian, и это нормально, если у вас нет слишком большого количества членов структуры. Но мы также можем использовать модификатор порядка байтов для группы и записать следующее:
my $gappy = pack( '( c x![s] s c x![l] l )<', $c1, $s, $c2, $l ); Это не так кратко, как раньше, но это делает более очевидным, что мы намерены использовать порядок байтов little-endian для всей группы, а не только для отдельных кодов шаблона. Это также может быть более читабельным и более простым в обслуживании.
Выравнивание, версия 2
Боюсь, что мы еще не совсем закончили с проблемой выравнивания. Гидра поднимает другую уродливую голову, когда вы упаковываете массивы структур:
typedef struct {
short count;
char glyph;
} cell_t;
typedef cell_t buffer_t[BUFLEN]; В чем подвох? Заполнение не требуется ни перед первым полем count, ни между ним и следующим полем glyph, так почему же мы не можем просто упаковать так:
# something goes wrong here:
pack( 's!a' x @buffer,
map{ ( $_->{count}, $_->{glyph} ) } @buffer ); Это упаковывает 3*@buffer байт, но оказывается, что размер buffer_t в четыре раза больше BUFLEN! Мораль истории такова, что требуемое выравнивание структуры или массива распространяется на следующий более высокий уровень, где мы должны учитывать заполнение в конце каждого компонента. Таким образом, правильный шаблон:
pack( 's!ax' x @buffer,
map{ ( $_->{count}, $_->{glyph} ) } @buffer ); Выравнивание, версия 3
И даже если вы учтете все вышесказанное, ANSI по-прежнему позволяет:
typedef struct {
char foo[2];
} foo_t; изменяться в размерах. Ограничение выравнивания структуры может быть больше, чем у любого из ее элементов. [И если вы думаете, что это не влияет ни на что общее, разоберите следующую сотовую телефон. У многих есть ядра ARM, и правила выравнивания структуры ARM делают sizeof (foo_t) == 4]
Указатели для использования
Название этого раздела указывает на вторую проблему, с которой вы можете столкнуться рано или поздно, когда будете упаковывать C-структуры. Если функция, которую вы хотите вызвать, ожидает, скажем, значение void *, вы не можете просто взять ссылку на переменную Perl. (Хотя это значение определенно является адресом памяти, это не адрес, где хранится содержимое переменной.)
Шаблонный код P обещает упаковать «указатель на строку фиксированной длины». Разве это не то, что нам нужно? Давайте попробуем:
# allocate some storage and pack a pointer to it
my $memory = "\x00" x $size;
my $memptr = pack( 'P', $memory ); Но подождите: разве pack просто не возвращает последовательность байтов? Как мы можем передать эту последовательность байтов некоторому коду на C, ожидающему указатель, который, в конце концов, представляет собой лишь число? Ответ прост: мы должны получить числовой адрес из байтов, возвращённых pack.
my $ptr = unpack( 'L!', $memptr ); Очевидно, это предполагает, что можно выполнить приведение типа указателя к типу unsigned long и наоборот, что часто работает, но не следует считать универсальным законом. — Теперь, когда у нас есть этот указатель, следующий вопрос: как мы можем его использовать? Нам нужен вызов некоторой функции C, где ожидается указатель. Вспомним системный вызов read(2):
ssize_t read(int fd, void *buf, size_t count); После прочтения perlfunc, объясняющего, как использовать syscall, мы можем написать эту Perl-функцию, копирующую файл в стандартный вывод:
require 'syscall.ph'; # run h2ph to generate this file
sub cat($){
my $path = shift();
my $size = -s $path;
my $memory = "\x00" x $size; # allocate some memory
my $ptr = unpack( 'L', pack( 'P', $memory ) );
open( F, $path ) || die( "$path: cannot open ($!)\n" );
my $fd = fileno(F);
my $res = syscall( &SYS_read, fileno(F), $ptr, $size );
print $memory;
close( F );
} Это не образец простоты и не образец переносимости, но это иллюстрирует суть: мы можем проникнуть за кулисы и получить доступ к памяти Perl, которая обычно хорошо защищена! (Важное примечание: Perl's syscall не требует от вас создания указателей таким окольным путём. Вы просто передаёте переменную строки, а Perl передаёт адрес.)
Как работает unpack с P? Представьте себе указатель в буфере, который вот-вот будет распакован: Если это не нулевой указатель (который умно производит значение undef), у нас есть начальный адрес — но что дальше? Perl не знает, какой длины эта «строка фиксированной длины», поэтому вам нужно указать фактический размер как явную длину после P.
my $mem = "abcdefghijklmn";
print unpack( 'P5', pack( 'P', $mem ) ); # prints "abcde" Вследствие этого, pack игнорирует любое число или * после P.
Теперь, когда мы увидели P в действии, мы можем попробовать p. Зачем нам вообще нужен второй шаблонный код для упаковки указателей? Ответ кроется в простом факте, что unpack с p обещает нуль-терминированную строку, начинающуюся с адреса, взятого из буфера, и это подразумевает длину элемента данных, который будет возвращён:
my $buf = pack( 'p', "abc\x00efhijklmn" );
print unpack( 'p', $buf ); # prints "abc" Хотя это может быть запутанно: поскольку длина подразумевается длиной строки, число после кода pack p представляет собой количество повторений, а не длину, как после P.
Использование pack(..., $x) с P или p для получения адреса, где фактически хранится $x, требует осмотрительности. Внутренняя система Perl рассматривает взаимоотношения между переменной и этим адресом как свой собственный частный вопрос и не заботится о том, что мы получили копию. Поэтому:
-
Не используйте
packсpилиPдля получения адреса переменной, область видимости которой закончится (и тем самым освободит её память) до того, как вы закончите использовать память по этому адресу. -
Будьте очень осторожны с операциями Perl, которые изменяют значение переменной. Например, добавление чего-либо к переменной может потребовать перераспределения её памяти, что оставит вас с указателем в ничейной земле.
-
Не думайте, что вы можете получить адрес переменной Perl, когда она хранится как целое число или число с плавающей точкой!
pack('P', $x)принудительно преобразует внутреннее представление переменной в строку, точно так же, как если бы вы написали что-то вроде$x .= ''.
Однако безопасно использовать P- или p-упаковку литерала строки, потому что Perl просто выделяет анонимную переменную.
Рецепты pack
Вот коллекция (возможно) полезных готовых рецептов для pack и unpack:
# Convert IP address for socket functions
pack( "C4", split /\./, "123.4.5.6" );
# Count the bits in a chunk of memory (e.g. a select vector)
unpack( '%32b*', $mask );
# Determine the endianness of your system
$is_little_endian = unpack( 'c', pack( 's', 1 ) );
$is_big_endian = unpack( 'xc', pack( 's', 1 ) );
# Determine the number of bits in a native integer
$bits = unpack( '%32I!', ~0 );
# Prepare argument for the nanosleep system call
my $timespec = pack( 'L!L!', $secs, $nanosecs ); Для простого вывода памяти мы распаковываем некоторые байты в такое же количество пар шестнадцатеричных цифр и используем map для обработки традиционного форматирования — 16 байтов в строке:
my $i;
print map( ++$i % 16 ? "$_ " : "$_\n",
unpack( 'H2' x length( $mem ), $mem ) ),
length( $mem ) % 16 ? "\n" : ''; Раздел юмора
# Pulling digits out of nowhere...
print unpack( 'C', pack( 'x' ) ),
unpack( '%B*', pack( 'A' ) ),
unpack( 'H', pack( 'A' ) ),
unpack( 'A', unpack( 'C', pack( 'A' ) ) ), "\n";
# One for the road ;-)
my $advice = pack( 'all u can in a van' ); Авторы
Саймон Коузенс и Вольфганг Лаун.
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.30.3/perlpacktut