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 ); Это верно для всех кодов шаблонов чисел. Но не ждите чудес: если упакованное значение превышает выделенную ёмкость байтов, старшие биты молча отбрасываются, и распаковка, безусловно, не сможет извлечь их из волшебной шляпы. И когда вы упаковываете, используя знаковый код шаблона, такой как 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. То же самое очевидно работает для <, где «маленькая часть» касается кода.
Вы, вероятно, найдёте эти модификаторы ещё более полезными, если вам придётся иметь дело с big- или little-endian структурами C. Обязательно прочитайте "Упаковка и распаковка структур 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 бита можно игнорировать.
Кодирование UU
Ещё одна особенность в алфавите шаблонов — 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 символов: символы Basic Latin назначаются числам от 0 до 127. Следующий диапазон до 255 содержит дополнение Latin-1 с символами, используемыми на нескольких европейских языках. После нескольких дополнительных расширений Latin мы находим наборы символов языков, использующих нелатинские алфавиты, чередующиеся с различными наборами символов, такими как валютные символы, 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 байтовой строки в строку Perl Unicode и Encode::encode('UTF-8', $str) для кодирования строки Perl Unicode в байты UTF-8. Эти функции обеспечивают возможность обработки недопустимых последовательностей байтов и, как правило, имеют более удобный интерфейс.
Другое портативное бинарное кодирование
Код pack w был добавлен для поддержки портативного бинарного кодирования данных, которое выходит за рамки простых целых чисел. (Подробности можно найти на http://Casbah.org/, проект Scarab.) Сжатое беззнаковое целое число BER (Binary Encoded Representation) хранит цифры по основанию 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-умножения строк шаблонов. Теперь есть лучший способ, так как мы можем использовать коды pack ( и ) в сочетании со счётчиком повторений. Шаблон unpack из примера Stack Frame можно просто записать так:
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 ) ); Наконец, есть и совершенно другой подход — распаковать короткие целые числа big endian и упаковать их в обратном порядке байтов:
$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 ); / не реализован в Perls до 5.6, поэтому, если ваш код должен работать в старых версиях Perls, вам придётся unpack( 'Z* Z* C') получить длину, а затем использовать её для создания новой строки распаковки. Например
# 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.
Подсчет повторений
Вместо хранения стоп-символа в конце элемента данных (или списка элементов), мы могли бы поместить счёт перед данными. Снова, мы упаковываем ключи и значения хеша, помещая перед каждым из них счётчик unsigned short длины, и в самом начале храним число пар:
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 бит), вычисленную как младший байт дополнительного кода по модулю 2 суммы предшествующих байтов. Пример: :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-битное операнд может перемещаться быстрее между местами в памяти или в/из регистра процессора, если он выровнен по чётному или кратно четырём или даже по кратно восьми адресу, 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 ); Это заставляет члены short и long быть 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 ); Очевидно, это предполагает, что можно привести указатель к типу беззнакового длинного целого и наоборот, что часто работает, но не должно рассматриваться как универсальный закон. - Теперь, когда у нас есть этот указатель, следующий вопрос: как мы можем им воспользоваться? Нам нужен вызов некоторой функции 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.28.3/perlpacktut