Spec-Zone.ru › Perl 5.32

perlpacktut

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
  • Основной принцип
  • Упаковывание текста
  • Упаковывание чисел
    • Целые числа
    • Распаковка стекового кадра
    • Как съесть яйцо на сетке
    • Модификаторы порядка байтов
    • Числа с плавающей точкой
  • Экзотические шаблоны
    • Битовые строки
    • Uuencoding
    • Выполнение сумм
    • Unicode
    • Другое портативное двоичное кодирование
  • Группировка шаблонов
  • Длины и ширины
    • Длины строк
    • Динамические шаблоны
    • Счёт повторений
    • Intel HEX
  • Упаковывание и распаковывание C-структур
    • Яма выравнивания
    • Работа с эндианностью
    • Выравнивание, повтор 2
    • Выравнивание, повтор 3
    • Указатели для того, как ими пользоваться
  • Рецепты 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 );

Это верно для всех числовых кодов шаблона. Но не ждите чудес: если упакованное значение превышает выделенный объём байтов, старшие биты молча отбрасываются, и распаковка определённо не сможет извлечь их из какой-то волшебной шляпы. И когда вы упаковываете, используя знаковый код шаблона, такой как s, избыточное значение может привести к установке бита знака, и распаковка разумно вернёт отрицательное значение.

16 бит не позволят вам далеко продвинуться с целыми числами, но есть l и L для знаковых и беззнаковых 32-битных целых чисел. И если этого недостаточно, и ваша система поддерживает 64-битные целые числа, вы можете значительно расширить пределы с помощью кодов упаковки q и Q. Заметным исключением являются коды упаковки i и I для знаковых и беззнаковых целых чисел «локального типа»: такое целое число займёт столько байт, сколько возвращает локальный компилятор C для sizeof(int), но будет использовать по крайней мере 32 бита.

Каждый из кодов упаковки целых чисел sSlLqQ приводит к фиксированному количеству байт независимо от того, где вы выполняете свою программу. Это может быть полезно для некоторых применений, но не обеспечивает переносной способ передачи структур данных между Perl и программами C (что обязательно произойдёт при вызове расширений XS или функции Perl syscall), или при чтении или записи двоичных файлов. В этом случае вам понадобятся коды шаблонов, которые зависят от того, что ваш локальный компилятор C компилирует, когда вы пишете short или unsigned long, например. Эти коды и их соответствующие длины байтов показаны в таблице ниже. Поскольку стандарт C предоставляет много свободы в отношении относительных размеров этих типов данных, фактические значения могут варьироваться, и именно поэтому значения даются в виде выражений на C и Perl. (Если вы хотите использовать значения из %Config в своей программе, вам нужно импортировать их с помощью use Config.)

signed unsigned  byte length in C   byte length in Perl       
  s!     S!      sizeof(short)      $Config{shortsize}
  i!     I!      sizeof(int)        $Config{intsize}
  l!     L!      sizeof(long)       $Config{longsize}
  q!     Q!      sizeof(long long)  $Config{longlongsize}

Коды i! и I! не отличаются от i и I; они допускаются для полноты.

Распаковка кадра стека

Запрос конкретного порядка байтов может быть необходим при работе с двоичными данными, поступающими от определённой архитектуры, в то время как ваша программа может работать на совершенно другой системе. Например, предположим, что у вас есть 24 байта, содержащие кадр стека, как это происходит на Intel 8086:

     +---------+        +----+----+               +---------+
TOS: |   IP    |  TOS+4:| FL | FH | FLAGS  TOS+14:|   SI    |
     +---------+        +----+----+               +---------+
     |   CS    |        | AL | AH | AX            |   DI    |
     +---------+        +----+----+               +---------+
                        | BL | BH | BX            |   BP    |
                        +----+----+               +---------+
                        | CL | CH | CX            |   DS    |
                        +----+----+               +---------+
                        | DL | DH | DX            |   ES    |
                        +----+----+               +---------+

Во-первых, мы отмечаем, что эта проверенная временем 16-битная процессор использует порядок little-endian, и именно поэтому младший байт хранится по более низкому адресу. Для распаковки такого (беззнакового) короткого числа нам придётся использовать код v. Повторяющееся число распаковывает все 12 коротких чисел:

my( $ip, $cs, $flags, $ax, $bx, $cd, $dx, $si, $di, $bp, $ds, $es ) =
  unpack( 'v12', $frame );

В качестве альтернативы, мы могли бы использовать C для распаковки отдельных регистров байтов FL, FH, AL, AH и т. д.:

my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
  unpack( 'C10', substr( $frame, 4, 10 ) );

Было бы неплохо сделать это одним махом: распаковать короткое число, немного вернуться назад и затем распаковать 2 байта. Поскольку Perl — это хорошая вещь, он предлагает код шаблона X для отката на один байт. Объединив всё это, мы можем теперь написать:

my( $ip, $cs,
    $flags,$fl,$fh,
    $ax,$al,$ah, $bx,$bl,$bh, $cx,$cl,$ch, $dx,$dl,$dh, 
    $si, $di, $bp, $ds, $es ) =
unpack( 'v2' . ('vXXCC' x 5) . 'v5', $frame );

(Неуклюжая конструкция шаблона может быть избегнута — просто читайте дальше!)

Мы постарались составить шаблон таким образом, чтобы он соответствовал содержимому нашего буфера кадра. В противном случае мы либо получили бы неопределённые значения, либо unpack не смог бы распаковать всё. Если pack закончится элементами, он обеспечит нулевые строки (которые преобразуются в нули всякий раз, когда код упаковки этого требует).

Как съесть яйцо на сети

Код упаковки для big-endian (старший байт в наименьшем адресе) составляет n для 16-битных и N для 32-битных целых чисел. Вы используете эти коды, если знаете, что ваши данные поступают от совместимой архитектуры, но, удивительно, вы также должны использовать эти коды упаковки, если обмениваетесь двоичными данными через сеть с системой, о которой вы почти ничего не знаете. Простая причина заключается в том, что этот порядок был выбран как сетевой порядок, и все программы, уважающие стандарты, должны следовать этой конвенции. (Это, конечно, строгая поддержка одной из лиллипутских партий и может повлиять на политическое развитие там.) Таким образом, если протокол ожидает, что вы отправите сообщение, отправив сначала длину, а затем столько байт, вы могли бы написать:

my $buf = pack( 'N', length( $msg ) ) . $msg;

или даже:

my $buf = pack( 'NA*', length( $msg ), $msg );

и передать $buf своей процедуре отправки. Некоторые протоколы требуют, чтобы счёт включал в себя длину самого счёта: затем просто добавьте 4 к длине данных. (Но убедитесь, что вы прочитали "Длины и ширины", прежде чем действительно набирать код!)

Модификаторы порядка байтов

В предыдущих разделах мы узнали, как использовать n, N, v и V для упаковки и распаковки целых чисел с порядком байтов big- или little-endian. Хотя это неплохо, это всё ещё довольно ограниченно, потому что оно оставляет вне всяких знакомых целых чисел, а также 64-битных целых чисел. Например, если вы хотите распаковать последовательность знакомых целых чисел big-endian по 16 бит независимо от платформы, вам придётся написать:

my @data = unpack 's*', pack 'S*', unpack 'n*', $buf;

Это некрасиво. Начиная с Perl 5.9.2, есть намного лучший способ выразить своё желание относительно определённого порядка байтов: модификаторы > и <. > — это модификатор big-endian, а < — little-endian. Используя их, мы могли бы переписать предыдущий код как:

my @data = unpack 's>*', $buf;

Как вы можете видеть, «большой конец» стрелки касается s, что является хорошим способом запомнить, что > — это модификатор big-endian. То же самое, очевидно, работает и для <, где «маленький конец» касается кода.

Вы, вероятно, найдёте эти модификаторы ещё более полезными, если вам нужно иметь дело со структурами C big- или little-endian. Обязательно прочитайте "Упаковка и распаковка структур C" для получения дополнительной информации об этом.

Числа с плавающей точкой

Для упаковки чисел с плавающей точкой у вас есть выбор между кодами упаковки f, d, F и D. f и d упаковывают в (или распаковывают из) представление одинарной или двойной точности, как это предоставляется вашей системой. Если ваша система это поддерживает, D может быть использован для упаковки и распаковки (long double) значений, что может предложить ещё больше разрешения, чем f или d. Обратите внимание, что существуют различные форматы long double.

F упаковывает NV, что является типом с плавающей точкой, используемым Perl внутри.

Нет такого понятия, как сетевое представление для вещественных чисел, поэтому, если вы хотите отправлять ваши вещественные числа через границы компьютеров, лучше всего придерживаться текстового представления, возможно, используя шестнадцатеричный формат чисел с плавающей точкой (избегая потери точности при десятичном преобразовании), если вы не уверены, что находится на другом конце линии. Для ещё более смелых, вы можете использовать модификаторы порядка байтов из предыдущего раздела также для кодов с плавающей точкой.

Экзотические шаблоны

Битовые строки

Биты — это атомы в мире памяти. Доступ к отдельным битам может потребоваться либо в качестве крайнего средства, либо потому, что это наиболее удобный способ обработки ваших данных. Битовая строка (распаковка) преобразует между строками, содержащими серию 0 и 1 символов, и последовательностью байтов, каждый из которых содержит группу из 8 бит. Это почти так же просто, как звучит, за исключением двух способов, которыми содержимое байта может быть записано как битовая строка. Давайте рассмотрим аннотированный байт:

  7 6 5 4 3 2 1 0
+-----------------+
| 1 0 0 0 1 1 0 0 |
+-----------------+
 MSB           LSB

Это снова едоки яиц: Некоторые считают, что как битовая строка это должно быть написано «10001100», то есть начиная со старшего бита, другие настаивают на «00110001». Ну, Perl не предвзятый, поэтому у нас есть два кода битовых строк:

$byte = pack( 'B8', '10001100' ); # start with MSB
$byte = pack( 'b8', '00110001' ); # start with LSB

Невозможно упаковать или распаковать битовые поля — только целые байты. pack всегда начинается с следующей границе байта и «округляется» до следующей кратной 8, добавляя нулевые биты по необходимости. (Если вам нужны битовые поля, есть "vec" в perlfunc. Или вы можете реализовать обработку битовых полей на уровне строковых символов, используя split, substr и конкатенацию для распакованных битовых строк.)

Чтобы проиллюстрировать распаковку битовых строк, мы разложим простой регистр состояния (дефис — это «зарезервированный» бит):

+-----------------+-----------------+
| S Z - A - P - C | - - - - O D I T |
+-----------------+-----------------+
 MSB           LSB MSB           LSB

Преобразование этих двух байтов в строку можно выполнить с шаблоном распаковки 'b16'. Чтобы получить отдельные битовые значения из битовой строки, мы используем split с пустым разделительным шаблоном, который разделяет значения на отдельные символы. Значения битов из «зарезервированных» позиций просто присваиваются undef, удобной записи для «мне все равно, куда это пойдёт».

($carry, undef, $parity, undef, $auxcarry, undef, $zero, $sign,
 $trace, $interrupt, $direction, $overflow) =
   split( //, unpack( 'b16', $status ) );

Мы также могли использовать шаблон распаковки 'b12', так как последние 4 бита можно проигнорировать.

Кодирование UU

Ещё одна «особенность» в алфавите шаблонов — u, которое упаковывает «строку, закодированную в формате uuencoding». («uu» — сокращение от Unix-to-Unix.) Вероятность того, что вам когда-либо понадобится этот метод кодирования, невелика, так как он был придуман для преодоления недостатков устаревших сред передачи, которые поддерживают только простые данные ASCII. Суть проста: возьмите три байта или 24 бита. Разделите их на 4 группы по 6 бит, добавляя пробел (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 символов: символы Основной латиницы назначены числам от 0 до 127. Дополнительные символы латиницы с символами, используемыми на нескольких европейских языках, находятся в следующем диапазоне до 255. После нескольких расширений латиницы мы находим наборы символов языков, использующих нелатинские алфавиты, перемежающиеся с различными наборами символов, такими как символы валют, Zapf Dingbats или шрифт Брайля. (Вы можете посетить https://www.unicode.org/, чтобы ознакомиться с некоторыми из них — мои личные фавориты — телугу и каннада.)

Набор символов Unicode связывает символы с целыми числами. Кодирование этих чисел в равном количестве байтов более чем удвоило бы требования к хранению текстов, написанных на латинских алфавитах. Кодировка UTF-8 избегает этого, храня наиболее распространённые (с точки зрения западного мира) символы в одном байте, а реже встречающиеся — в трёх или более байтах.

Perl использует UTF-8 для большинства строк Unicode внутри.

Так что же это имеет общего с pack? Ну, если вы хотите составить строку Unicode (которая закодирована как UTF-8 внутри), вы можете сделать это, используя шаблон кодирования U. В качестве примера давайте произведём символ европейской валюты (код 0x20AC):

$UTF8{Euro} = pack( 'U', 0x20AC );
# Equivalent to: $UTF8{Euro} = "\x{20ac}";

Проверка $UTF8{Euro} показывает, что она содержит 3 байта: «\xe2\x82\xac». Однако она содержит только 1 символ, число 0x20AC. Полный цикл можно выполнить с помощью unpack:

$Unicode{Euro} = unpack( 'U', $UTF8{Euro} );

Распаковка с использованием шаблона U кодирования также работает со строками байтов, закодированных в UTF-8.

Обычно вы хотите упаковывать или распаковывать строки UTF-8:

# pack and unpack the Hebrew alphabet
my $alefbet = pack( 'U*', 0x05d0..0x05ea );
my @hebrew = unpack( 'U*', $utf );

Обратите внимание: в общем случае лучше использовать Encode::decode('UTF-8', $utf) для декодирования строки байтов UTF-8 в строку Unicode Perl и Encode::encode('UTF-8', $str) для кодирования строки Unicode Perl в байты UTF-8. Эти функции предоставляют средства для обработки недопустимых последовательностей байтов и, как правило, имеют более дружественный интерфейс.

Другая портативная двоичная кодировка

Код упаковки w добавлен для поддержки портативной двоичной схемы кодирования данных, выходящей за рамки простых целых чисел. (Подробности см. в https://github.com/mworks-project/mw_scarab/blob/master/Scarab-0.1.00d19/doc/binary-serialization.txt, проект Scarab.) Сжатое целое беззнаковое число BER (двоичное кодированное представление) хранит цифры в системе счисления по основанию 128, старшая цифра первой, с минимальным количеством цифр. Бит восемь (старший бит) установлен в каждом байте, за исключением последнего. Нет ограничений на размер кодирования BER, но Perl не пойдёт на крайности.

my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 );

Шестнадцатеричный дамп $berbuf, с вставленными пробелами в нужных местах, показывает 01 8100 8101 81807F. Поскольку последний байт всегда меньше 128, unpack знает, где остановиться.

Группировка шаблонов

До Perl 5.8 повторения шаблонов приходилось создавать с помощью x-умножения строк шаблонов. Теперь есть лучший способ, так как мы можем использовать коды упаковки ( и ) в сочетании со счётчиком повторений. Шаблон unpack из примера стека кадров можно записать так:

unpack( 'v2 (vXXCC)5 v5', $frame )

Давайте немного глубже разберёмся с этой функцией. Начнём с эквивалента

join( '', map( substr( $_, 0, 1 ), @str ) )

который возвращает строку, состоящую из первого символа каждой строки. С помощью функции pack мы можем записать

pack( '(A)'.@str, @str )

или, так как счётчик повторений * означает «повторять столько раз, сколько потребуется», просто

pack( '(A)*', @str )

(Обратите внимание, что шаблон A* упаковал бы только $str[0] в полную длину.)

Чтобы упаковать даты, хранящиеся как тройки (день, месяц, год) в массиве @dates в последовательность байт, байт, целого короткого числа, можно написать

$pd = pack( '(CCS)*', map( @$_, @dates ) );

Чтобы поменять местами пары символов в строке (чётной длины), можно использовать несколько методов. Сначала используем x и X для перемещения вперёд и назад:

$s = pack( '(A)*', unpack( '(xAXXAx)*', $s ) );

Мы также можем использовать @ для перехода к смещению, где 0 — позиция, на которой мы находились, когда последний ( был встречен:

$s = pack( '(A)*', unpack( '(@1A @0A @2)*', $s ) );

Наконец, есть также совершенно другой подход, основанный на распаковке коротких чисел большого порядка и упаковке их в обратном порядке байтов:

$s = pack( '(v)*', unpack( '(n)*', $s );

Длины и ширины

Длина строк

В предыдущем разделе мы видели сетевое сообщение, которое было сконструировано путём добавления длины двоичного сообщения к самому сообщению. Вы обнаружите, что упаковка длины, за которой следуют столько байтов данных, — часто используемый приём, поскольку добавление нулевого байта не сработает, если нулевой байт может быть частью данных. Вот пример, где используются оба метода: после двух строк с нулевым завершением адресами источника и назначения отправляется короткое сообщение (мобильному телефону) после байта длины:

my $msg = pack( 'Z*Z*CA*', $src, $dst, length( $sm ), $sm );

Распаковать это сообщение можно с тем же шаблоном:

( $src, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg );

Есть одна тонкая ловушка: добавление другого поля после короткого сообщения (в переменной $sm) в порядке при упаковке, но это нельзя распаковать нехитрым способом:

# pack a message
my $msg = pack( 'Z*Z*CA*C', $src, $dst, length( $sm ), $sm, $prio );

# unpack fails - $prio remains undefined!
( $src, $dst, $len, $sm, $prio ) = unpack( 'Z*Z*CA*C', $msg );

Код упаковки 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 );

/ не реализовано в Perлах до версии 5.6, поэтому, если ваш код должен работать в старых версиях Perла, вам необходимо 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.

Подсчёт Повторений

Вместо хранения стоп-символа в конце элемента данных (или списка элементов), мы могли бы предварять данные подсчётом. Опять же, мы упаковываем ключи и значения хеша, предваряя каждый из них счётчиком длины без знака 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 — это формат файла для представления двоичных данных, в основном для программирования различных чипов, как текстовый файл. (См. https://en.wikipedia.org/wiki/.hex для подробного описания и https://en.wikipedia.org/wiki/SREC_(file_format) для формата Motorola S-record, который можно разобрать с помощью той же техники.) Каждая строка начинается с двоеточия (':') и за ним следует последовательность шестнадцатеричных символов, задающих счётчик байтов n (8 бит), адрес (16 бит, big endian), тип записи (8 бит), n байтов данных и контрольную сумму (8 бит), вычисляемую как наименее значимый байт суммы дополнения до двух предыдущих байтов. Пример: :0300300002337A1E.

Первый шаг обработки такой строки — это преобразование шестнадцатеричных данных в двоичный вид, для получения четырёх полей, при проверке контрольной суммы. Здесь нет ничего удивительного: мы начнём с простого вызова pack для преобразования всего в двоичный вид:

my $binrec = pack( 'H*', substr( $hexrec, 1 ) );

Полученная последовательность байтов наиболее удобна для проверки контрольной суммы. Не замедляйте свою программу циклом for, добавляющим ord значения байтов этой строки — код unpack % является тем, что нужно использовать для вычисления 8-битной суммы всех байтов, которая должна быть равна нулю:

die unless unpack( "%8C*", $binrec ) == 0;

Наконец, давайте получим эти четыре поля. К этому моменту у вас не должно быть проблем с первыми тремя полями — но как мы можем использовать количество байтов данных в первом поле в качестве длины для поля данных? Здесь на помощь приходят коды x и X, поскольку они позволяют переходить вперёд и назад в строке для распаковки.

my( $addr, $type, $data ) = unpack( "x n C X4 C x3 /a", $bin ); 

Код x пропускает байт, так как нам пока не нужно значение счётчика. Код n обрабатывает 16-битное целое число адреса big-endian, и C распаковывает тип записи. Будучи в смещении 4, где начинаются данные, нам нужен счётчик. X4 возвращает нас к исходному положению, которое является байтом со смещением 0. Теперь мы выбираем счётчик и стремительно перемещаемся к смещению 4, где мы полностью оснащены, чтобы извлечь точное количество байтов данных, оставив последний байт контрольной суммы в покое.

Упаковку и Распаковку C Структур

В предыдущих разделах мы видели, как упаковывать числа и строки символов. Если бы не было пары проблем, мы могли бы заключить этот раздел сразу же кратким замечанием, что C структуры не содержат ничего другого, и поэтому вы уже знаете всё, что нужно. К сожалению, нет: читайте дальше, пожалуйста.

Если вам нужно иметь дело с множеством C структур и вы не хотите вручную вносить изменения во все ваши строковые шаблоны, вы, вероятно, захотите взглянуть на модуль CPAN Convert::Binary::C. Он не только может анализировать ваш C-исходный код напрямую, но также имеет встроенную поддержку всех дополнительных деталей, описанных далее в этом разделе.

Яма Выравнивания

При рассмотрении скорости по сравнению с требованиями к памяти баланс был смещён в пользу более быстрого выполнения. Это повлияло на способ, которым C компиляторы выделяют память для структур: на архитектурах, где 16-битное или 32-битное операнд может быть перемещено быстрее между местами в памяти или к/из регистра процессора, если он выровнен по чётному или кратному четырём или даже кратному восьми адресу, 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, где shorts, ints и longs все являются 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 );

Очевидно, это предполагает, что можно привести указатель к типу unsigned long и наоборот, что часто работает, но не должно рассматриваться как универсальный закон. - Теперь, когда у нас есть этот указатель, следующий вопрос: как мы можем им эффективно воспользоваться? Нам нужен вызов некоторой функции C, где ожидается указатель. В голову приходит системный вызов read(2):

ssize_t read(int fd, void *buf, size_t count);

После прочтения perlfunc, объясняющего, как использовать syscall, мы можем написать эту функцию Perl, копирующую файл в стандартный вывод:

require 'syscall.ph'; # run h2ph to generate this file
sub cat($){
    my $path = shift();
    my $size = -s $path;
    my $memory = "\x00" x $size;  # allocate some memory
    my $ptr = unpack( 'L', pack( 'P', $memory ) );
    open( F, $path ) || die( "$path: cannot open ($!)\n" );
    my $fd = fileno(F);
    my $res = syscall( &SYS_read, fileno(F), $ptr, $size );
    print $memory;
    close( F );
}

Это ни образец простоты, ни образец переносимости, но это иллюстрирует суть: мы можем проникнуть за кулисы и получить доступ к памяти Perl, которая обычно хорошо защищена! (Важное примечание: Perl's syscall не требует, чтобы вы строили указатели таким окольным способом. Вы просто передаёте переменную строки, и Perl передаёт адрес.)

Как работает unpack с P? Представьте себе указатель в буфере, который вот-вот будет распакован: Если это не нулевой указатель (который умно производит значение undef), у нас есть начальный адрес - но что потом? Perl не знает, какой длины эта "строка фиксированной длины", поэтому вам нужно указать фактический размер как явную длину после P.

my $mem = "abcdefghijklmn";
print unpack( 'P5', pack( 'P', $mem ) ); # prints "abcde"

Вследствие этого, pack игнорирует любое число или * после P.

Теперь, когда мы увидели P в действии, мы можем попробовать p. Зачем нам нужен второй шаблонный код для упаковки указателей? Ответ кроется в простом факте, что unpack с p обещает строку с нулевым окончанием, начинающуюся с адреса, взятого из буфера, и это подразумевает длину элемента данных, который будет возвращен:

my $buf = pack( 'p', "abc\x00efhijklmn" );
print unpack( 'p', $buf );    # prints "abc"

Хотя это может быть запутанно: так как длина подразумевается длиной строки, число после кода pack p — это счётчик повторений, а не длина, как после P.

Использование pack(..., $x) с P или p для получения адреса, где $x фактически хранится, следует применять с осторожностью. Внутренний механизм Perl рассматривает отношение между переменной и этим адресом как свою собственную частную проблему и не заботится о том, что мы получили копию. Поэтому:

  • Не используйте pack с p или P для получения адреса переменной, которая может выйти за пределы области видимости (и тем самым освободить свою память) до того, как вы закончите использовать память по этому адресу.

  • Будьте очень осторожны с операциями Perl, которые изменяют значение переменной. Например, добавление чего-либо к переменной может потребовать перераспределения её памяти, оставив вас с указателем в неопределённой области.

  • Не думайте, что вы можете получить адрес переменной Perl, когда она хранится как целое или двойное число! pack('P', $x) приведёт внутреннее представление переменной к строке, точно так же, как будто вы написали что-то вроде $x .= ''.

Однако безопасно использовать P- или p-упаковку литерала строки, так как Perl просто выделяет анонимную переменную.

Рецепты упаковки

Вот набор (возможно) полезных рецептов для pack и 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.32.0/perlpacktut

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API