Spec-Zone.ru › Perl 5.38

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 символов. "Символ" по-%%CODE_BLOCK_42%%%-ски - это 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. То же самое, очевидно, работает для <, где «маленький конец» касается кода.

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

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

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

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

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

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

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

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

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

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

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

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

Чтобы проиллюстрировать распаковку битовых строк, мы разложим простой регистр состояния (“-” обозначает «зарезервированный» бит):

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

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

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

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

Uuencoding

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

my $uubuf = pack( 'u', $bindat );

Количество повторений после u задаёт число байтов, помещаемых в строку uuencoded, которое по умолчанию составляет максимум 45, но может быть установлено в любое целое кратное трём. unpack просто игнорирует счётчик повторений.

Вычисление сумм

Ещё более странным кодом шаблона является %<число>. Во-первых, потому что он используется как префикс к некоторым другим кодам шаблонов. Во-вторых, потому что он не может быть использован в pack вообще, а в-третьих, в unpack, не возвращает данные, определённые кодом шаблона, которому он предшествует. Вместо этого он вернёт целое число из число бит, вычисленное из значения данных путём суммирования. Для числовых кодов распаковки ничего существенного не достигается:

my $buf = pack( 'iii', 100, 20, 3 );
print unpack( '%32i3', $buf ), "\n";  # prints 123

Для строковых значений % возвращает сумму значений байтов, избавляя вас от необходимости цикла суммирования с substr и ord.

print unpack( '%32A*', "\x01\x10" ), "\n";  # prints 17

Хотя код % документирован как возвращающий «контрольную сумму», не полагайтесь на такие значения! Даже при применении к небольшому числу байтов они не гарантируют заметной дистанции Хэмминга.

В связи с b или B, % просто добавляет биты, и это можно использовать для эффективного подсчёта установленных битов:

my $bitcount = unpack( '%32b*', $mask );

И бит чётности можно определить следующим образом:

my $evenparity = unpack( '%1b*', $mask );

Unicode

Unicode — это набор символов, который может представлять большинство символов большинства языков мира, предоставляя место для более чем миллиона различных символов. Unicode 3.1 определяет 94 140 символов: символы Basic Latin присвоены номерам от 0 до 127. Последующий набор Latin-1 Supplement со символами, используемыми на нескольких европейских языках, находится в следующем диапазоне, до 255. После некоторых других расширений Latin мы находим наборы символов языков, использующих нелатинские алфавиты, перемежаемые различными наборами символов, такими как символы валют, 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». Однако она содержит только один символ, номер 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 из примера Stack Frame может быть записан так:

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

Давайте более подробно рассмотрим эту функцию. Начнём с эквивалента

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Длины строк

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Intel HEX

Intel HEX — это формат файла для представления двоичных данных, в основном для программирования различных микросхем, в виде текстового файла. (См. https://ru.wikipedia.org/wiki/.hex для подробного описания, и https://ru.wikipedia.org/wiki/SREC_(формат_файла) для формата Motorola S-record, который можно разобрать с помощью той же техники.) Каждая строка начинается с двоеточия (':') и за ним следует последовательность шестнадцатеричных символов, определяющих счётчик байтов n (8 бит), адрес (16 бит, старший байт слева), тип записи (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: Мы хотим убедиться, что мы упаковываем длинное целое число так, как его компилирует наш C-компилятор. И даже сейчас, это будет работать только на платформах, где компилятор выравнивает данные так, как описано выше. А где-то есть платформа, где это не так. [Вероятно, Cray, где shortы, intы и longы все по 8 байт. :-)]

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

#include <stdio.h>
#include <stddef.h>

typedef struct {
  char     fc1;
  short    fs;
  char     fc2;
  long     fl;
} gappy_t;

#define Pt(struct,field,tchar) \
  printf( "@%d%s ", offsetof(struct,field), # tchar );

int main() {
  Pt( gappy_t, fc1, c  );
  Pt( gappy_t, fs,  s! );
  Pt( gappy_t, fc2, c  );
  Pt( gappy_t, fl,  l! );
  printf( "\n" );
}

Строка вывода может использоваться в качестве шаблона в pack или unpack вызове:

my $gappy = pack( '@0c @2s! @4c @8l!', $c1, $s, $c2, $l );

Ого, ещё один шаблонный код — как будто нам недостаточно. Но @ спасает нас, позволяя нам указать смещение от начала буфера упаковки до следующего элемента: это просто значение, возвращаемое макросом offsetof (определённым в <stddef.h>) при передаче ему типа struct и одного из его имён полей («идентификатор члена» в стандарте C).

Ни использование смещений, ни добавление x для заполнения пробелов не является удовлетворительным. (Просто представьте, что произойдёт, если структура изменится.) Нам действительно нужен способ сказать «пропустить столько байтов, сколько необходимо, до следующего кратного N». В гибких шаблонах вы говорите это с помощью x!N, где N заменяется соответствующим значением. Вот следующая версия упаковки нашей структуры:

my $gappy = pack( 'c x!2 s c x!4 l!', $c1, $s, $c2, $l );

Это определённо лучше, но нам всё ещё нужно знать, какой длины все целые числа, и переносимость находится далеко. Вместо 2, например, мы хотим сказать «любой длины, которую занимает short». Но это можно сделать, заключив соответствующий код упаковки в скобки: [s]. Итак, вот самое лучшее, что мы можем сделать:

my $gappy = pack( 'c x![s] s c x![l!] l!', $c1, $s, $c2, $l );

Работа с Endian-ностью

Теперь представьте, что мы хотим упаковать данные для машины с другим порядком байтов. Сначала нам нужно будет выяснить, какой размер имеют типы данных на целевой машине. Предположим, что long имеют 32 бита, а short — 16 бит. Тогда вы можете переписать шаблон как:

my $gappy = pack( 'c x![s] s c x![l] l', $c1, $s, $c2, $l );

Если целевая машина имеет little-endian, мы можем написать:

my $gappy = pack( 'c x![s] s< c x![l] l<', $c1, $s, $c2, $l );

Это принудительно задаёт little-endian для short и long членов и вполне подходит, если у вас не слишком много членов структуры. Но мы также можем использовать модификатор порядка байтов для группы и написать следующее:

my $gappy = pack( '( c x![s] s c x![l] l )<', $c1, $s, $c2, $l );

Это не так коротко, как раньше, но делает более очевидным, что мы намерены использовать little-endian порядок байтов для целой группы, а не только для отдельных шаблонных кодов. Это также может быть более читаемым и легче поддерживаться.

Выравнивание, часть 2

Боюсь, мы ещё не совсем закончили с проблемой выравнивания. Гидра поднимает ещё одну уродливую голову, когда вы упаковываете массивы структур:

typedef struct {
  short    count;
  char     glyph;
} cell_t;

typedef cell_t buffer_t[BUFLEN];

В чём подвох? Заполнение не требуется ни перед первым полем count, ни между ним и следующим полем glyph, так почему же мы не можем просто упаковать так:

# something goes wrong here:
pack( 's!a' x @buffer,
      map{ ( $_->{count}, $_->{glyph} ) } @buffer );

Это упаковывает 3*@buffer байтов, но оказывается, что размер buffer_t в четыре раза больше BUFLEN! Мораль истории в том, что требуемое выравнивание структуры или массива распространяется на следующий более высокий уровень, где мы должны учитывать заполнение в конце каждого компонента. Таким образом, правильный шаблон — это:

pack( 's!ax' x @buffer,
      map{ ( $_->{count}, $_->{glyph} ) } @buffer );

Выравнивание, часть 3

И даже если вы учтёте всё вышесказанное, ANSI всё ещё позволяет:

typedef struct {
  char     foo[2];
} foo_t;

изменяться в размере. Требование выравнивания структуры может быть больше, чем любой из её элементов. [И если вы думаете, что это не влияет на что-то общее, разоберите следующий телефон, который вы видите. Многие имеют ARM-ядра, а правила выравнивания ARM делают sizeof (foo_t) == 4]

Указатели: как их использовать

Название этого раздела указывает на вторую проблему, с которой вы можете столкнуться рано или поздно, когда вы упаковываете C-структуры. Если функция, которую вы собираетесь вызвать, ожидает, скажем, значение void *, вы не можете просто взять ссылку на переменную Perl. (Хотя это значение, безусловно, является адресом памяти, это не адрес, где хранится содержимое переменной.)

Шаблонный код P обещает упаковать «указатель на строку фиксированной длины». Разве это не то, что нам нужно? Попробуем:

# allocate some storage and pack a pointer to it
my $memory = "\x00" x $size;
my $memptr = pack( 'P', $memory );

Но подождите: pack просто возвращает последовательность байтов? Как мы можем передать эту последовательность байтов некоторому C-коду, ожидающему указатель, который, в конце концов, ни что иное, как число? Ответ прост: Мы должны получить числовой адрес из байтов, возвращённых pack.

my $ptr = unpack( 'L!', $memptr );

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

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

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

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

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

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

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

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

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

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

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

Spec-Zone.ru

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