Глава 16: Доступ к системным базам данных.
16.2 Упаковка и распаковка двоичных данных
Данные о паролях и группах удобно использовать в текстовом виде. Информацию в других системных базах данных более естественно представлять иначе. Например, IP-адрес интерфейса обрабатывается внутренними механизмами как четырехбайтовое число. Хотя его часто представляют в текстовом виде (как четыре небольших целых числа, разделенных точками), такое преобразование — пустая трата времени, если эти данные в промежутке между преобразованиями не выводятся на зкран пользователя.
По этой причине написанные на Perl сетевые программы, ожидающие или возвращающие IP-адрес, используют четырехбайтовую строку, одному символу которой соответстввует один байт в памяти. Хотя конструирование и интерпретация такой байтовой строки — довольно простая задача, решаемая с помощью функций chr и ord (здесь не представленных), в Perl используется более эффективное решение, которое в равной степени применимо и к более сложньш структурам.
Функция pack по принципу работы немного похожа на функцию sprintf. Она получает строку, задающую формат, и список значений и упаковывает значения в одну строку. Однако в pack строка, задающая формат, предназначена для создания двоичной структури данных. Напри-мер, вот как можно взять четыре небольших целых числа и упаковать их в виде последовательности байтов без знака в строке:
$buf = pack("CCCC", 140, 186, 65, 25);
Здесь строка формата pack — четыре буквы С. Каждая С соответствует отдельному значенню, взятому из приведенного следом списка (подобно тому, что делает спецификация % в функций sprintf). Формат С (согласно man-страницам Perl, краткому справочнику, книге Programming Perl, HTML-файлам и даже видеоролику Perl: The Motion Picture) обозначает один байт, вычисляемый из символьного значения без знака (короткого целого). Строка-результат в переменной $buf представляет собой четырехсимвольную строку, в которой каждый символ задан одним байтом. Эти байты имеют значения 140, 186, 65 и 25 соответственно.
Аналогичным образом формат 1 генерирует длинное значение со знаком. На многих машинах это четырехбайтовое число, хотя этот формат зависит от конкретной машины. На четырехбайтовой "длинной" машине оператор
$buf = pack("l",0х41424344) ;
генерирует четырехсимвольную строку, состоящую из символов abcd или dcba -- в зависимости от того, какой порядок хранения байтов используется на данной машине: "младший в младшем" или "старший в младшем" (либо что-то совершенно иное, если эта машина "не говорит" на ASCII). Это обіясняется тем, что мы упаковываем одно значение в четыре символа (для представлення длинного целого отводится четыре байта), а это одно значение как раз состоит из байтов, представляющих коды ASCII первых четырех букв алфавита. Аналогичным образом,
$buf = pack("ll", 0х41424344, 0х45464748);
создает восьмибайтовую строку, состоящую из букв abcdefgh или dcbahgfe, опять-таки в зависимости от того, какой порядок хранения байтов используется в данной машине — "младший в младшем" или "старший в младшем".
Полный перечень различных форматов, используемых для упаковки, приведен в справочной документации (perlfunc(l) или Programming Perl). Мы приведем некоторые из них как примеры, но все, конечно, давать не будем.
Допустим, вам дали восьмибайтовую строку abcdefgh и сказали, что она является представлением хранящихся в памяти (один символ — один байт) двух длинных (четырехбайтовых) значений со знаком. Как ее интерпретиро-вать? Нужно воспользоваться функцией, обратной функции pack,— функцией unpack. Она берет строку управления форматом (как правило, иден-тичную той, которую вы указывали в функции pack) и строку данных и возвращает список значений, которые хранятся в соответствующих ячейках памяти. Давайте, например, распакуем такую строку:
($vall,$val2) = unpack("ll","ABCDEFGH");
Это даст нам в переменной $vall нечто вроде 0х41424344, а может быть, и 0х44434241 (в зависимости от порядка хранения байтов). По сути дела, по возвращаемым значениям мы можем определить, на какой машине работаем — с порядком "младший в младшем" или "старший в младшем".
Пробельные символы в строке, задающей формат, игнорируются и используются лишь для удобочитаемости. Число в этой строке, как правило, задает повторение предндущей спецификации соответствующее количество раз. Например, сссс можно записать как С4 или С2С2, смысл от этого не изменится. (Однако в некоторых спецификациях число, указанное после символа, задающего формат, является частью спецификации, поэтому их подобным образом записывать нельзя.)
После символа формата может стоять также звездочка, которая задает повторное применение данного формата до тех пор, пока не обработана остальная часть списка значений или пока не создана остальная часть строки, содержащей двоичное представление (в зависимости от того, что выполняется — упаковка или распаковка). Вот еще один способ упаковки четырех символов без знака в одну строку:
$bu£ ” pack("C*", 140, 186, 65, 25);
Здесь указанные четыре значения полностью обрабатываются одной спецификацией формата. Если бы вам требовались два коротких целых и "максимально возможное количество символов без знака", то можно было бы написать примерно так:
Здесь мы получаем первые два значения как короткие (и генерируем, вероятно, четыре или восемь символов), а остальные девять — как символы без знака (й генерируем, почти наверняка, девять символов).
Функция unpack co звездочкой в качестве спецификации может формировать список элементов, длина которых заранее не определена. Например, при распаковке с использованием формата с* создается один элемент списка (число) для каждого символа строки. Так, оператор
@values = unpack("С*", "hello, world!\n");
позволяет сформировать список из 14 элементов, по одному для каждого символа строки.