perlpacktut
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- Основной принцип
- Упаковывание текста
- Упаковывание чисел
- Экзотические шаблоны
- Группировка шаблонов
- Длина и ширина
- Упаковывание и распаковывание C структур
- Рецепты pack
- Раздел с юмором
- Авторы
ИМЯ
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, $cx, $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 бита можно игнорировать.
Кодирование Uuencoding
Другим необычным символом в алфавите шаблона является u, который упаковывает «строку, закодированную в формате uuencoding». («uu» — сокращение от Unix-to-Unix.) Вероятность, что вам когда-либо понадобится этот метод кодирования, невелика, так как он был разработан для преодоления недостатков устаревших каналов передачи данных, которые поддерживают только простые данные ASCII. Суть рецепта проста: Возьмите три байта или 24 бита. Разделите их на 4 шестерки, добавив пробел (0x20) к каждой. Повторяйте, пока все данные не будут объединены. Разбейте группы из 4 байтов на строки длиной не более 60 символов и поместите в начало количество исходных байтов (увеличенное на 0x20) и символ "\n" в конце. - Шеф-повар pack подготовит это для вас, на месте, когда вы выберете код упаковки u из меню:
my $uubuf = pack( 'u', $bindat ); Счетчик повторений после u устанавливает количество байтов, помещаемых в строку, закодированную в uuencoding, которое по умолчанию составляет максимум 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. Латинский дополнение 1 с символами, которые используются на нескольких европейских языках, находится в следующем диапазоне, до 255. После нескольких расширений латинских символов мы находим наборы символов с языков, использующих нелатинские алфавиты, перемежаемые с различными наборами символов, такими как символы валют, Zapf Dingbats или шрифт Брайля. (Возможно, вам стоит посетить https://www.unicode.org/, чтобы посмотреть некоторые из них — мои личные фавориты — телугу и каннада.)
Набор символов Unicode связывает символы с целыми числами. Кодирование этих чисел в равном количестве байтов более чем удвоило бы требования к хранению текстов, написанных латинскими алфавитами. Кодирование UTF-8 избегает этого, храня наиболее распространённые (с точки зрения запада) символы в одном байте, а реже встречающиеся — в трёх или более байтах.
Perl использует UTF-8 внутри для большинства строк Unicode.
Так что же это имеет общего с pack? Ну, если вы хотите составить строку Unicode (которая закодирована внутри в UTF-8), вы можете сделать это, используя код шаблона U. В качестве примера давайте создадим символ евро (€):
$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. Эти функции предоставляют средства для обработки недопустимых последовательностей байтов и, как правило, имеют более дружественный интерфейс.
Другое портативное двоичное кодирование
Код упаковки w был добавлен для поддержки портативного двоичного кодирования данных, которое выходит за рамки простых целых чисел. (Подробности можно найти по адресу https://github.com/mworks-project/mw_scarab/blob/master/Scarab-0.1.00d19/doc/binary-serialization.txt, проект Scarab.) Сжатое беззнаковое целое число BER (Binary Encoded Representation) хранит цифры по основанию 128, старшая цифра первой, с как можно меньшим количеством цифр. Бит восемь (старший бит) устанавливается в каждом байте, за исключением последнего.
my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 ); Шестнадцатеричный вывод $berbuf, со вставленными пробелами в нужных местах, показывает 01 8100 8101 81807F. Так как последний байт всегда меньше 128, unpack знает, где остановиться.
Группирование шаблонов
До Perl 5.8 повторения шаблонов необходимо было делать путём x-умножения строк шаблона. Теперь есть лучший способ, поскольку мы можем использовать коды упаковки ( и ) в сочетании со счётчиком повторений. Шаблон 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 ); Код упаковки 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, длина аргумента берётся и упаковывается в соответствии с первым кодом, в то время как сам аргумент добавляется после преобразования с помощью кода шаблона после косой черты. Это избавляет нас от необходимости вставки length вызова, но именно в unpack мы действительно выигрываем: Значение байта длины обозначает конец строки, которая должна быть взята из буфера. Поскольку это сочетание не имеет смысла, кроме как когда второй код упаковки не a*, A* или Z*, Perl не позволит вам этого.
Код упаковки, предшествующий /, может быть любым, что подходит для представления числа: все числовые двоичные коды упаковки, а также текстовые коды, такие как 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') для получения длины, а затем использовать её для создания новой строки распаковки. Например
# 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 слишком спешит вперёд. Она не использует простую строку-литерал в качестве шаблона. Так что, может быть, нам следует ввести...
Динамические шаблоны
До сих пор мы использовали литералы в качестве шаблонов. Если у списка элементов упаковки нет фиксированной длины, требуется выражение, которое строит шаблон (всякий раз, когда по какой-либо причине ()* не может быть использован). Вот пример: для хранения именованных строковых значений таким образом, чтобы их удобно было анализировать программе 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 — это формат файла для представления двоичных данных, в основном для программирования различных микросхем, в виде текстового файла. (См. https://ru.wikipedia.org/wiki/.hex для подробного описания и https://ru.wikipedia.org/wiki/SREC_(формат_файла) для формата Motorola S-record, который можно разобрать с помощью той же техники.) Каждая строка начинается с двоеточия (':') и за ним следует последовательность шестнадцатеричных символов, определяющих счетчик байтов n (8 бит), адрес (16 бит, big endian), тип записи (8 бит), n байтов данных и контрольную сумму (8 бит), вычисляемую как младший байт дополнительного кода к двум от суммы предыдущих байтов. Пример: :0300300002337A1E.
Первый шаг обработки такой строки — преобразование шестнадцатеричных данных в двоичный формат, чтобы получить четыре поля, при этом проверяя контрольную сумму. Здесь нет сюрпризов: мы начнем с простого вызова pack для преобразования всего в двоичный формат:
my $binrec = pack( 'H*', substr( $hexrec, 1 ) ); Полученная последовательность байтов наиболее удобна для проверки контрольной суммы. Не замедляйте свою программу циклом for, суммируя значения байтов этой строки — используйте код 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: мы хотим убедиться, что мы упаковываем длинное целое число так, как его компилирует наш компилятор 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 и одного из его имен полей ("member-designator" в терминологии стандарта C).
Ни использование смещений, ни добавление x для устранения разрывов не является удовлетворительным. (Просто представьте, что произойдет, если структура изменится.) Нам действительно нужен способ сказать "пропустить столько байтов, сколько нужно, до следующего кратного N". В гибких шаблонах вы говорите об этом с помощью x!N, где N заменяется соответствующим значением. Вот следующая версия нашей упаковки структур:
my $gappy = pack( 'c x!2 s c x!4 l!', $c1, $s, $c2, $l ); Это определенно лучше, но мы все еще должны знать, какой длины все целые числа, и переносимость все еще далека. Вместо 2, например, мы хотим сказать «любой длины, какой есть у короткого целого». Но это можно сделать, заключив соответствующий код упаковки в скобки: [s]. Итак, вот лучшее, что мы можем сделать:
my $gappy = pack( 'c x![s] s c x![l!] l!', $c1, $s, $c2, $l ); Обработка порядка байтов
Теперь представьте, что мы хотим упаковать данные для машины с другим порядком байтов. Сначала нам нужно будет определить, какой размер данных типов на целевой машине. Предположим, что длинные целые числа имеют ширину 32 бита, а короткие целые числа — 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 ); Очевидно, это предполагает, что можно привести указатель к беззнаковому длинному целому и наоборот, что часто работает, но не должно рассматриваться как универсальный закон. — Теперь, когда у нас есть этот указатель, следующий вопрос: как мы можем его использовать? Нам нужен вызов некоторой 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–2021 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.36.0/perlpacktut