Spec-Zone.ru › Perl 5.34

perlpacktut

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
  • Основные принципы
  • Упаковывание текста
  • Упаковывание чисел
    • Целые числа
    • Распаковка кадра стека
    • Как есть яйцо на сетке
    • Модификаторы порядка байтов
    • Числа с плавающей точкой
  • Экзотические шаблоны
    • Битовые строки
    • Ууэнкодирование
    • Вычисление сумм
    • Юникод
    • Другое портативное двоичное кодирование
  • Группировка шаблонов
  • Длина и ширина
    • Длина строк
    • Динамические шаблоны
    • Подсчёт повторений
    • Intel HEX
  • Упаковывание и распаковывание C-структур
    • Яма выравнивания
    • Обработка порядка байтов
    • Выравнивание, вариант 2
    • Выравнивание, вариант 3
    • Указатели для использования
  • Рецепты упаковки
  • Раздел смешных моментов
  • Авторы

НАЗВАНИЕ

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 — шириной десять символов. 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, $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

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

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

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

Uuencoding

Ещё одним «отличником» в алфавите шаблонов является u, который упаковывает «uuencoded строку». («uu» — сокращение от Unix-to-Unix.) Вероятность, что вам когда-либо понадобится этот метод кодирования, невелика, он был придуман для преодоления недостатков устаревших каналов передачи данных, которые поддерживают только простые данные ASCII. Суть проста: взять три байта или 24 бита. Разделить их на 4 шестибитовых блока, добавив пробел (0x20) к каждому. Повторять до тех пор, пока все данные не будут объединены. Складывать группы из 4 байтов в строки не длиннее 60 символов, поместив в начало количество исходных байтов (увеличенных на 0x20) и "\n" в конце. — Повар pack подготовит это для вас, по заказу, когда вы выберите код 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. Латинский дополнительный набор с символами, используемыми на нескольких европейских языках, находится в следующем диапазоне, до 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. Эти функции обеспечивают способы обработки недопустимых последовательностей байтов и обычно имеют более удобный интерфейс.

Ещё один портативный двоичный формат кодирования

Код pack 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-умножения строк шаблонов. Теперь есть лучший способ, так как мы можем использовать коды pack ( и ) в сочетании с счётчиком повторений. Шаблон 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 ) );

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

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

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

Длины строк

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

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

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

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

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

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

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

Код pack A* схватывает все оставшиеся байты, а $prio остаётся неопределённым! Прежде чем разочарование подорвёт моральный дух: у Perl есть козырь, чтобы решить эту задачу. Посмотрите:

# pack a message: ASCIIZ, ASCIIZ, length/string, byte
my $msg = pack( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );

# unpack
( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg );

Комбинирование двух кодов pack с косой чертой (/) связывает их с единственным значением из списка аргументов. В pack, длина аргумента берётся и упаковывается в соответствии с первым кодом, а сам аргумент добавляется после преобразования с кодом шаблона после косой черты. Это избавляет нас от необходимости вставки length, но это в unpack, где мы действительно выигрываем: значение байта длины обозначает конец строки, которую следует взять из буфера. Поскольку этот комбинация имеет смысл только в том случае, если второй код pack не a*, A* или Z*, Perl не позволит вам этого сделать.

Код pack, предшествующий /, может быть любым, способным представлять число: все числовые двоичные коды pack и даже текстовые коды, такие как A4 или Z*:

# pack/unpack a string preceded by its length in ASCII
my $buf = pack( 'A4/A*', "Humpty-Dumpty" );
# unpack $buf: '13  Humpty-Dumpty'
my $txt = unpack( 'A4/A*', $buf );

/ не реализована в Perl до версии 5.6, поэтому, если ваш код должен работать на старых версиях Perl, вам нужно unpack( 'Z* Z* C'), чтобы получить длину, а затем использовать её для создания новой строки unpack. Например,

# pack a message: ASCIIZ, ASCIIZ, length, string, byte
# (5.005 compatible)
my $msg = pack( 'Z* Z* C A* C', $src, $dst, length $sm, $sm, $prio );

# unpack
( undef, undef, $len) = unpack( 'Z* Z* C', $msg );
($src, $dst, $sm, $prio) = unpack ( "Z* Z* x A$len C", $msg );

Но тот второй unpack спешит вперёд. Он не использует простую строку-литерал в качестве шаблона. Так что, возможно, мы должны ввести...

Динамические шаблоны

До сих пор мы видели литералы, используемые в качестве шаблонов. Если список элементов пакета не имеет фиксированной длины, требуется выражение, строящее шаблон (в тех случаях, когда по какой-то причине ()* нельзя использовать). Вот пример: Чтобы хранить именованные строковые значения таким образом, чтобы их удобно было анализировать программе на C, мы создаём последовательность имён и завершаемых нулём ASCII-строк, с = между именем и значением, а затем дополнительный разделительный нулевой байт. Вот как:

my $env = pack( '(A*A*Z*)' . keys( %Env ) . 'C',
                map( { ( $_, '=', $Env{$_} ) } keys( %Env ) ), 0 );

Давайте рассмотрим шестерёнки этой байтовой мельницы по одной. Вот вызов map, создающий элементы, которые мы собираемся поместить в буфер $env: для каждого ключа (в $_) он добавляет разделитель = и значение записи хеша. Каждый тройной набор упаковывается с помощью последовательности кода шаблона A*A*Z*, которая повторяется в соответствии с количеством ключей. (Да, именно это возвращает функция keys в скалярном контексте.) Чтобы получить последний нулевой байт, мы добавим 0 в конце списка pack, который будет упакован с помощью C. (Внимательные читатели, возможно, заметили, что мы могли бы опустить 0.)

Для обратной операции нам нужно определить количество элементов в буфере, прежде чем мы сможем позволить unpack разорвать его:

my $n = $env =~ tr/\0// - 1;
my %env = map( split( /=/, $_ ), unpack( "(Z*)$n", $env ) );

tr подсчитывает нулевые байты. Вызов unpack возвращает список пар имя-значение, каждая из которых разбирается в блоке map.

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

Вместо хранения стоп-символа в конце элемента данных (или списка элементов), мы могли бы поместить счётчик в начало. Опять же, мы упаковываем ключи и значения хеша, предваряя каждый из них счётчиком длины целого без знака (unsigned short), и в начале храним число пар:

my $env = pack( 'S(S/A* S/A*)*', scalar keys( %Env ), %Env );

Это упрощает обратную операцию, так как количество повторений можно распаковать с помощью кода /:

my %env = unpack( 'S/(S/A* S/A*)', $env );

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

Intel HEX

Intel HEX — это формат файла для представления двоичных данных, в основном для программирования различных микросхем, в виде текстового файла. (См. 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, складывая значения 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-битный целое число с большим эндианом, а 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 и одного из его имён полей («указатель члена» на стандартном 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 );

Очевидно, это предполагает, что можно выполнить приведение типа указателя к 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, которая, в противном случае, хорошо защищена! (Важное примечание: 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"

Хотя это может быть немного запутанно: так как длина подразумевается длиной строки, число после кода паковки 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–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.34.0/perlpacktut

Spec-Zone.ru

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