perlpacktut - pack
と unpack
のチュートリアル
pack
と unpack
は、ユーザーが定義したテンプレートに従って、
Perl が値を保管する保護された方法と、Perl プログラムの環境で必要になる
かもしれないよく定義された表現の間を変換する二つの関数です。
残念ながら、これらは Perl が提供する関数の中でもっとも誤解され、
もっとも見落とされやすい関数でもあります。
このチュートリアルではこれらを分かりやすく説明します。
多くのプログラミング言語は変数が格納されているメモリを保護していません。
例えば、C では、ある変数のアドレスを取得できますし、
sizeof
演算子は変数に何バイト割り当てられているかを返します。
アドレスとサイズを使って、心臓部にあるストレージにアクセスできます。
Perl では、メモリにランダムにアクセスすることはできませんが、pack
と
unpack
によって提供される構造的および表現的な変換は素晴らしい
代替案です。
pack
関数は、値を、「テンプレート」引数と呼ばれる使用に従った表現を含む
バイト列に変換します。
unpack
は逆処理で、バイトの並びから値を引き出します。
(しかし、pack された全てのデータがうまく unpack できるというわけでは
ないということは注意してください - 経験豊かな旅人が確認しそうな、とても
一般的な経験です。)
あなたはどうしてバイナリ表現の中に値が含まれているメモリの塊が
必要なのか、と問うかもしれません。
よい理由の一つは、ファイル、デバイス、ネットワーク接続にアクセスする
入出力で、このバイナリ表現が強制されたものか、処理するためにいくらかの
利益がある場合です。
もう一つの原因は、Perl 関数として利用できないシステムコールにデータを
渡すときです: syscall
は C プログラムでのような形で保管された引数を
提供することを要求します。
(以下の章で示すように) テキスト処理ですら、これら 2 つの関数を賢明に
使うことで単純化できます。
(un)pack がどのように働くのかを見るために、変換がのろのろと行われる単純な
テンプレートコードから始めましょう: バイトシーケンスと 16 進数の文字列との
変換です。
unpack
を使いましょう; なぜならこれはダンププログラムや、
不幸なプログラムが息を引き取る前にあなたに投げかけることが常となっている
絶望的な最後のメッセージを思い出させそうだからです。
変数 $mem
に、その意味について何の仮定もおかずに調査したいバイト列が
入っていると仮定すると、以下のように書きます:
my( $hex ) = unpack( 'H*', $mem ); print "$hex\n";
するとすぐに、1 バイトに対応して 16 進数 2 文字が対応する、以下のような ものが表示されます:
41204d414e204120504c414e20412043414e414c2050414e414d41
このメモリの塊はなんでしょう?
数値、文字、あるいはそれらの混合でしょうか?
使っているコンピュータが ASCII エンコーディング (あるいは似たようなもの) を
使っていると仮定します: 0x40
- 0x5A
範囲の 16 進数は大文字を
示していて、0x20
は空白をエンコードしたものです。
それで、これは、タブロイドのように読むことのできるテキストの断片と
仮定できます; その他は ASCII テーブルを持って 1 年生の感覚を思い出す
必要があります。
これをどのようにして読むかについてはあまり気にしないことにして、
unpack
のテンプレートコード H
はバイト列の内容をいつもの 16 進表記に
変換することに注目します。
「列」というのは量についてあいまいなので、
H
は、引き続いて繰り返し回数がない場合は単に 1 つの 16 進数を
変換するように定義されています。
繰り返し数でのアスタリスクは、残っているもの全てを使うことを意味します。
逆操作 - 16 進数の文字列からバイトの内容に pack する - は簡単に書けます。 例えば:
my $s = pack( 'H2' x 10, map { "3$_" } ( 0..9 ) ); print "$s\n";
16 進で 2 桁の数値を示す文字列 10 個からなるリストを pack
に
渡しているので、pack テンプレートは 10 個の pack コードを含んでいる
必要があります。
これが ASCII 文字コードのコンピュータで実行されると、0123456789
を
表示します。
以下のようなデータファイルを読み込むことを考えます:
Date |Description | Income|Expenditure 01/24/2001 Ahmed'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 文字の幅があることが
わかります。
「文字」のパックは A
で、10 文字の場合は A10
です。
それで、もし単に日付を展開したいだけなら、以下のように書けます:
my($date) = unpack("A10", $_);
よろしい、次は?
日付と説明の間には空白の桁があります;これは読み飛ばしたいです。
x
テンプレートは「読み飛ばす」ことを意味し、
これで 1 文字読み飛ばせます。
次に、別の文字の塊が 12 桁から 38 桁まであります。
これは 27 文字あるので、A27
です。
(数え間違えないように - 12 から 38 の間には 26 ではなく 27 文字あります。)
次の文字は読み飛ばして、次の 7 文字を取り出します:
my($date,$description,$income) = unpack("A10xA27xA7", $_);
ここで少し賢くやりましょう。
台帳のうち、収入だけがあって支出がない行は 46 行目で終わっています。
従って、次の 12 文字を見つける 必要がある ということを
unpack
パターンに書きたくはありません;
単に次のようにします「もし何かが残っていれば、それを取ります」。
正規表現から推測したかもしれませんが、これが *
の意味することです:
「残っているもの全てを使います」。
但し、正規表現とは違うことに注意してください。
もし unpack
テンプレートが入力データと一致しない場合、
Perl は悲鳴をあげて die します。
従って、これを全部あわせると:
my($date,$description,$income,$expend) = unpack("A10xA27xA7xA*", $_);
これで、データがパースできます。 今ほしいものが収入と支出をそれぞれ足し合わせて、台帳の最後に - 同じ形式で - 1 行付け加えることで、どれだけの収入と支出があったかを記すことだとします:
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 Ahmed'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 in perlfunc に書いていることを見てみましょう:
x A null byte.
うはあ。 当たり前です。 文字コード 0 の「ヌル文字」と、文字コード 32 の「空白」は全然違います。 Perl は日付と説明の間に何かを書いたのです - しかし残念ながら、 それは見えません!
実際に必要なことはフィールドの幅を増やすことです。
A
フォーマットは存在しない文字を空白でパッディングするので、
以下のようにフィールドに空白の分だけ桁数を増やします:
print pack("A11 A28 A8 A*", $date, "Totals", $tot_income, $tot_expend);
(テンプレートには読みやすくするために空白を入れることができますが、 出力には反映されないことに注意してください。) これで得られたのは以下のものです:
01/24/2001 Ahmed'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
を使います。
pack フォーマット A
は「任意の文字」を意味します; もし pack
中に
pack するものがなくなったら、pack
は残りを空白で埋めます。
unpack
での x
は「1 バイト読み飛ばす」ことを意味します;
pack
では、「ヌルバイトを生成する」ことを意味します -
これは、プレーンテキストを扱っている場合はおそらく望んでいるものでは
ないでしょう。
フォーマットの後に数値をつけることで、フォーマットに影響される文字数を
指定します: A12
は「12 文字取る」ことを意味します;
x6
は「6 バイト読み飛ばす」や「ヌルバイト 6 つ」を意味します。
数値の代わりに、*
で「残っているもの全てを使う」ことを指定できます。
警告: 複数のデータ片を pack するとき、*
は「現在のデータ片を全て
含む」という意味だけです。
これは、以下のようにすると:
pack("A*A*", $one, $two)
$one
の全てを最初の A*
に pack し、それから $two
の全てを二番目に
pack します。
ここに一般的な原則があります: 各フォーマット文字は pack
されるデータ片
一つに対応します。
テキストデータについてはこれくらいです。
pack
と unpack
が最良である、いやらしい代物: 数値のためのバイナリ
フォーマットに進みましょう。
もちろん、バイナリフォーマットはひとつではありません - 人生はそれほど
単純ではありません - が、Perl は全ての細かい作業を行います。
数値を pack や unpack するということは、特定の バイナリ表現との間で 変換するということを意味します。 今のところ浮動小数点数は脇にやっておくとすると、このような表現の 主要な性質としては:
整数の保存に使うバイト数。
内容を符号なし数として解釈するか符号付き数として解釈するか。
バイト順序:最初のバイトは最下位バイトか最上位バイトか (言い換えると: それぞれリトルエンディアンかビッグエンディアンか)。
それで、例えば、20302 をあなたのコンピュータの符号付き 16 ビット整数に pack するとすると、以下のように書きます:
my $ps = pack( 's', 20302 );
再び、結果は 2 バイトからなる文字列です。
もしこの文字列を表示する(これは一般的にはお勧めできません)と、
ON
か NO
(システムのバイト順に依存します) - または、もし
コンピューターが ASCII 文字エンコーディングを使っていないなら全く違う
文字列が表示されます。
$ps
を同じテンプレートで unpack すると、元の整数値が返ります:
my( $s ) = unpack( 's', $ps );
これは全ての数値テンプレートコードに対して真です。
しかし奇跡を期待してはいけません:
もし pack された値が割り当てられたバイト容量を超えると、高位ビットは
黙って捨てられ、unpack は確実に魔法の帽子からデータを
取り出すことができません。
そして、s
のような符号付きテンプレートコードを使って pack すると、
超えた値が符号ビットをセットすることになり、unpack すると負の値が
返されることになるかもしれません。
16 ビットは整数に十分とは言えませんが、符号付きと符号なしの 32 ビット
整数のための l
と L
もあります。
そして、これで十分ではなく、システムが 64 ビット整数に対応しているなら、
pack コード q
と Q
を使って限界をほぼ無限にまで押しやることができます。
注目すべき例外は pack コード i
と I
で、「ローカルに特化した」
符号付きと符号なしの整数を提供します: このような整数は sizeof(int)
と
したときにローカルな C コンパイラが返す値と同じバイト数ですが、
少なくとも 32 ビットを使います。
整数 pack コード sSlLqQ
のそれぞれは、どこでプログラムが
実行されたとしても固定長のバイト列となります。
これは一部のアプリケーションでは有用ですが、(XS エクステンションや
Perl 関数 syscall
を呼び出すときに必要となる)
Perl と C のプログラムの間でデータ構造を渡す場合や、バイナリファイルを
読み書きするときの、移植性のある手段は提供しません
この場合に必要なものは、例えば、short
や unsigned long
と書いたときに
ローカルの C コンパイラがどのようにコンパイルするかに依存する
テンプレートコードです。
これらのコードと、それに対応するバイト長は以下のテーブルの様になります。
C 標準はそれぞれのデータ型の大きさの点で多くの自由裁量を残しているので、
実際の値は異なるかもしれず、そしてこれがなぜ値が C と Perl の式として
与えられているかの理由です。
(もしプログラムで %Config
の値を使いたい場合は、use Config
として
これをインポートする必要があります。)
符号付き 符号なし C でのバイト長 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
と違いはありません; これらは
完全性のために許容されています。
特定のアーキテクチャから来たバイナリに対して作業をする一方、プログラムが 全く違うシステムで動いている場合、特定のバイト順序の要求が必要になります。 例として、Intel 8086 のスタックフレームを含む 24 バイトを仮定します:
+---------+ +----+----+ +---------+ 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 ビット CPU はリトルエンディアンを使っていて、
それが低位バイトが低位アドレスに格納されている理由であることに
注意します。
このような(符号付き)short を unpack するには、コード v
を使う必要が
あるでしょう。
繰り返し数によって、12 全ての short を unpack します。
my( $ip, $cs, $flags, $ax, $bx, $cd, $dx, $si, $di, $bp, $ds, $es ) = unpack( 'v12', $frame );
あるいは、FL, FH, AL, AH といったバイトレジスタに個々にアクセスできるように
unpack するための C
もあります:
my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) = unpack( 'C10', substr( $frame, 4, 10 ) );
これを 1 回で行えたら素敵でしょう: short を unpack して、少し戻って、
それから 2 バイト unpack します。
Perl は 素敵 なので、1 バイト戻るテンプレートコード 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
は何も unpack できなくなります。
もし pack
で要素がなくなったら、空文字列を補います
(pack コードがそうするように言っていれば、ゼロに強制されます)。
ビッグエンディアン(最下位アドレスが最上位バイト)での pack コードは、
16 ビット整数が n
、 32 ビット整数が N
です。
もし準拠したアーキテクチャからデータが来ることが分かっているなら
これらのコードを使います;
もしネットワークを通して何も知らない他のシステムとバイナリデータを
交換する場合にもこれらの pack コードを使うべきです。
理由は単純で、この順序が ネットワーク順序 として選ばれていて、標準を
恐れる全てのプログラムがこの慣例に従っているはずだからです。
(これはもちろん小人族の一行の一人の厳しい支援で、政治的な発展に
影響を与えています。)
それで、もし何バイトあるかの長さを先に送ることでメッセージを送ることを
プロトコルが想定しているなら、以下のように書けます:
my $buf = pack( 'N', length( $msg ) ) . $msg;
あるいは:
my $buf = pack( 'NA*', length( $msg ), $msg );
そして $buf
を送信ルーチンに渡します。
カウントに、カウント自身の長さも含むことを要求しているプロトコルも
あります: その時は単にデータ長に 4 を足してください。
(しかし、実際にこれをコーディングする前に 長さと幅 を
読んでください!)
以前の章で、ビッグエンディアンとリトルエンディアンのバイト順の整数を
pack および unpack するための n
, N
, v
, V
の使い方を学びました。
これは素敵ですが、全ての種類の符号付き整数や、64 ビット整数が
外れているので、まだいくらか制限されたものです。
例えば、ビッグエンディアンの符号付き整数の並びをプラットフォームに
依存しない方法で unpack したいとすると、以下のように書かなければなりません:
my @data = unpack 's*', pack 'S*', unpack 'n*', $buf;
これは醜いです。
Perl 5.9.2 から、バイト順に関して望み通りに記述するための、遥かに
素敵な方法があります: >
と <
の修飾子です。
>
はビッグエンディアン修飾子で、<
は
リトルエンディアン修飾子です。
これらを使うと、上述のコードは以下のように書き換えられます:
my @data = unpack 's>*', $buf;
見た通り、不等号の「大きい側」が s
に向いていて、>
が
ビッグエンディアン修飾子であることを覚える素敵な方法となっています。
明らかに同じことが、「小さい側」がコードに向いている <
にも働きます。
おそらく、これらの修飾子はビッグエンディアンやリトルエンディアンの C 構造体を 扱うときにもっと便利であることに気付くでしょう。 これに関する詳細は、C の構造体を pack/unpack する を よく読んでください。
浮動小数点数を pack するには、pack コード f
, d
, F
, D
の
選択肢があります。
f
と d
pack はシステムで提供されている単精度と倍精度の実数に
pack (あるいは unpack) します。
もしシステムが対応していれば、f
や d
より精度のある、
拡張精度浮動小数点数 (long double
) の pack および unpack のために
D
が使えます。
F
は、Perl が内部で使用している浮動小数点型である NV
を pack します。
(実数に対してはネットワーク表現のようなものはないので、もし他の
コンピュータに実数を送りたい場合は、ネットワークの向こう側で何が起きるかが
完全に分かっているのでない限りは、ASCII 表現で我慢した方がよいです。
より冒険的な場合でも、以前の章で触れたバイト順修飾子を浮動小数点コードにも
使えます。)
ビットはメモリの世界の原子です。
個々のビットへのアクセスは、最後の手段として行われるか、それがデータを
扱うのに最も便利な方法であるときに行われます。
(un)pack したビット文字列は、0
と 1
の文字からなる文字列と、
それぞれ 8 ビットを含むバイト列とを変換します。
バイトの内容をビット文字列として書くには 2 つの方法があるということを除けば、
これはほとんど見たままの単純さです。
以下の注釈付きのバイトを見てみましょう:
7 6 5 4 3 2 1 0 +-----------------+ | 1 0 0 0 1 1 0 0 | +-----------------+ MSB LSB
卵の食べ方の繰り返しです: これは "10001100" というビット文字列になるべき、 つまり最上位ビットから始めるべき、と考える人もいますし、 "00110001" と主張する人もいます。 ええ、Perl は偏向していないので、これが 2 つのビット文字列コードがある 理由です:
$byte = pack( 'B8', '10001100' ); # start with MSB $byte = pack( 'b8', '00110001' ); # start with LSB
ビットフィールドを pack や unpack することはできません -
バイト単位だけです。
pack
は常に次のバイト境界から始まり、必要な場合は 0 のビットを
追加することで 8 の倍数に「切り上げ」られます。
(もしビットフィールドがほしいなら、vec in perlfunc があります。
あるいは、split, substr および unpack したビット文字列の結合を使って
文字単位のレベルでビットフィールド操作を実装することも出来ます。)
ビット文字列の unpack を図示するために、単純な状態レジスタを分解してみます ("-" は「予約された」ビットを意味します):
+-----------------+-----------------+ | S Z - A - P - C | - - - - O D I T | +-----------------+-----------------+ MSB LSB MSB LSB
これら 2 バイトから文字列への変換は unpack テンプレート 'b16'
によって
行われます。
ビット文字列から個々のビット値を得るには、split
を「空」セパレータで
使うことで個々の文字に切り刻みます。
「予約された」位置からのビット値は単に undef
に代入しておきます;
これは「この値がどこに行こうが気にしない」ことを示す便利な記法です。
($carry, undef, $parity, undef, $auxcarry, undef, $zero, $sign, $trace, $interrupt, $direction, $overflow) = split( //, unpack( 'b16', $status ) );
ちょうど同じように、unpack テンプレート 'b12'
も使えます;
最後の 4 ビットはどちらにしろ無視されるからです。
テンプレートの中のもう一つの半端者は u
で、「uuencode された文字列」を
pack します。
("uu" は Unix-to-Unix を縮めたものです。)
あなたには、単純な ASCII データしか対応していない旧式の通信メディアの欠点を
克服するために開発されたこのエンコーディング技術が必要になる機会は
なかったかもしれません。
本質的なレシピは単純です: 3 バイト、つまり 24 ビットを取ります。
これを 4 つの 6 ビットに分け、それぞれに空白 (0x20) を加えます。
全てのデータが混ぜられるまで繰り返します。
4 バイトの組を 60 文字を超えない行に折り畳み、元のバイト数(0x20 を
加えたもの)を先頭に置いて、末尾に "\n"
を置きます。
- あなたがメニューから pack コード u
を選ぶと、pack
シェフは
即席で、下ごしらえをしてくれます:
my $uubuf = pack( 'u', $bindat );
u
の後の繰り返し数は uuencode された行にいれるバイト数で、デフォルトでは
最大の 45 ですが、3 の倍数のその他の(より小さい)数にできます。
unpack
は単に繰り返し数を無視します。
さらに不思議なテンプレートコードは %
<number> です。
第一に、これはその他のテンプレートコードの前置詞として使われるからです。
第二に、pack
では全く使えず、第三に、unpack
では、先行する
テンプレートコードによって定義された値を返さないからです。
代わりに、これはデータの合計として計算された number ビットの整数を
与えます。
数値 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 は世界中のほとんどの言語のほとんどの文字を表現できる文字集合で、 100 万以上の異なった文字のための空間を提供しています。 Unicode 3.1 は 94,140 文字を定義しています: 基本ラテン文字は番号 0 - 127 に割り当てられています。 いくつかのヨーロッパ言語で使われるラテン 1 補助が次の範囲で、255 までです。 いくつかのラテン拡張の後、非ローマアルファベットを使う言語の文字集合 および、通貨記号、Zapf Dingbats、点字のような様々な記号集合が 散らばっています。 (これらのいくつかを見るために http://www.unicode.org/ を訪れるのも 良いでしょう - 私の個人的なお気に入りは Telugu と Kannada です。)
Unicode 文字集合は文字と整数を結び付けます。 これらの数値を同じバイト数でエンコードすると、ラテンアルファベットで 書かれたテキストを保管するのに 2 倍以上のバイト数が必要になります。 UTF-8 エンコーディングは(西洋からの視点において)もっとも共通の文字を 1 バイトに格納し、より稀なものを 3 バイト以上にエンコードすることで これを回避しています。
Perl はほとんどの Unicode 文字列に対して内部的に UTF-8 を使います。
それで、これで pack
は何ができるのでしょう?
えっと、もし Unicode 文字列 (これは内部では UTF-8 で
エンコードされています) を構成したいなら、テンプレートコード U
を
使うことでできます。
例として、ユーロ通貨記号 (コード番号 0x20AC) を生成してみましょう:
$UTF8{Euro} = pack( 'U', 0x20AC ); # Equivalent to: $UTF8{Euro} = "\x{20ac}";
$UTF8{Euro}
を検査すると、3 バイトであることがわかります:
"\xe2\x82\xac" です。
しかし、これは番号 0x20AC の 1 文字だけを含んでいます。
往復は unpack
を使って完了します:
$Unicode{Euro} = unpack( 'U', $UTF8{Euro} );
U
テンプレートコードを使った unpack テンプレートコードは
UTF-8 エンコードされたバイト文字列に対しても動作します。
普通は UTF-8 文字列を pack または unpack したいでしょう:
# pack and unpack the Hebrew alphabet my $alefbet = pack( 'U*', 0x05d0..0x05ea ); my @hebrew = unpack( 'U*', $utf );
注意: 一般的な場合には、UTF-8 エンコードされたバイト文字列を Perl の Unicode 文字列にデコードするには Encode::decode_utf8 を使い、 Perl の Unicode 文字列を UTF-8 のバイト文字列にエンコードするには Encode::encode_utf8 を使った方がよいです。 これらの関数は不正なバイト列を扱う手段を提供し、一般的により親切な インターフェースを持ちます。
pack コード w
は、単純な整数とは程遠い、移植性のある
バイナリデータエンコーディングスキームに対応するために追加されました。
(詳細については Scarab プロジェクト http://Casbah.org/ にあります。)
BER (Binary Encoded Representation) 圧縮符号なし整数は 128 を基数として、
最上位ビットを最初にして、可能な限り少ない桁になるように保管します。
ビット 8 (最上位ビット) は、最後以外のバイトでセットされます。
BER エンコーディングにはサイズ制限がありませんが、Perl は極端なことは
しません。
my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 );
$berbuf
を、適切な位置に空白を入れつつ 16 進ダンプを取ると、
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]
を 完全な長さで pack するだけという
ことに注意してください。)
配列 @dates
に 3 つ組 (日、月、年) として保管されている日付を
バイト、バイト、short に pack するには、以下のように書きます
$pd = pack( '(CCS)*', map( @$_, @dates ) );
ある文字列の中の(同じ長さの)部分文字列の組を交換するには、いくつかの
技が使えます。
まず、読み飛ばして戻ってくるために x
と X
を使いましょう:
$s = pack( '(A)*', unpack( '(xAXXAx)*', $s ) );
また、オフセットに飛ぶために @
も使えます; ここで 0 は最後に (
に
遭遇した位置になります:
$s = pack( '(A)*', unpack( '(@1A @0A @2)*', $s ) );
最後に、ビッグエンディアンの short として unpack して、逆のバイト順で pack するという、全く異なった手法もあります:
$s = pack( '(v)*', unpack( '(n)*', $s );
前の章で、実際のメッセージの前にメッセージの長さをバイナリで前置することで 構成されたネットワークメッセージを見ました。 NUL バイトを追加するという方法は、データの一部として NUL バイトが 含まれているときには動作しないので、引き続くデータの長さを pack するという 方法はよく見られます。 以下は両方の技術を使った例です: 送り元と送り先のアドレスを示す 2 つの NUL 終端文字列の後、(携帯電話への)ショートメッセージがその長さの後に 送られます:
my $msg = pack( 'Z*Z*CA*', $src, $dst, length( $sm ), $sm );
このメッセージを unpack するには同じテンプレートで可能です:
( $src, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg );
遠くに微妙な罠が顔を覗かせています: (変数 $sm
に入っている)
ショートメッセージの後にフィールドを追加すると、pack は問題ありませんが、
ネイティブに unpack 出来なくなります。
# 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 コードをスラッシュ (/
) で繋ぐことで、引数リストの 1 つの
値と結び付けられます。
pack
では、引数の長さが取られて最初のコードに従って pack される一方、
引数自体はスラッシュの後のテンプレートコードによって変換された後
追加されます。
これは length
呼び出しを挿入することによるトラブルを救いますが、
本当に効果があるのは unpack
においてです: 長さを示すバイトの値は
バッファから取られる文字列の末尾をマークします。
この組み合わせは 2 つ目の 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 );
/
は 5.6 以前の Perl には実装されていないので、もし、より古い 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 );
しかしこの 2 番目の unpack
は先走りました。
これはテンプレートとして単純なリテラル文字列を使っていません。
それでは説明するべきでしょう…
これまでは、テンプレートとして使われるリテラルを見てきました。
pack するアイテムのリストが固定長でない場合(何らかの理由で ()*
が
使えないなら)、テンプレートを構成する式が必要です。
以下は例です: C プログラムで使いやすい形で名前付き文字列値を保管するために、
一続きの名前と NUL 終端された ASCII 文字列を作ります;
名前と値の間には =
を置いて、最後に追加のデリミタとなる NUL バイトを
置きます。
以下のようにします:
my $env = pack( '(A*A*Z*)' . keys( %Env ) . 'C', map( { ( $_, '=', $Env{$_} ) } keys( %Env ) ), 0 );
このバイト処理機の要素を一つ一つ調査してみましょう。
map
呼び出しは、$env
バッファに入れることを想定している内容の
アイテムを作成します:
($_
の) それぞれのキーについて、=
セパレータとハッシュエントリの値を
追加します。
それぞれの 3 つ組は、キーの数
(はい、これは keys
関数がスカラコンテキストで返すものです。)
に従って繰り返されるテンプレートコードの
並び A*A*Z*
で pack されます。
まさに最後の NUL バイトを得るために、pack
リストの最後に C
で
pack するための 0
を追加します。
(注意深い読者なら、この 0 は省略できることに気付いたかもしれません。)
逆操作のために、unpack
に分解させる前にバッファにあるアイテムの数を
決定する必要があります:
my $n = $env =~ tr/\0// - 1; my %env = map( split( /=/, $_ ), unpack( "(Z*)$n", $env ) );
tr
はヌルバイトを数えます。
unpack
呼び出しは名前-値の組のリストを返し、そのそれぞれが
map
ブロックで分割されます。
データアイテム(あるいはアイテムのリスト)の最後に見張りをおくのではなく、 データの数を先においておくこともできます。 再び、ハッシュのキーと値を pack します; それぞれの前には符号なし short で 長さが置かれ、先頭には組の数を保管します:
my $env = pack( 'S(S/A* S/A*)*', scalar keys( %Env ), %Env );
繰り返し数は /
コードで unpack できるので、逆操作は単純になります:
my %env = unpack( 'S/(S/A* S/A*)', $env );
これは、pack
は ()
グループの繰り返し数を決定できないので、
pack
と unpack
で同じテンプレートが使えない珍しい場合であることに
注意してください。
Intel HEX バイナリデータを表現するためのファイル形式で、ほとんどの場合
様々なデータをテキストファイルとしてプログラミングするためのものです。
(詳細な記述については http://en.wikipedia.org/wiki/.hex を、
同じテクニックを使って展開できる Motorola S-record 形式については
http://en.wikipedia.org/wiki/SREC_(file_format) を参照してください。)
それぞれの行はコロン (':') で始まり、バイトカウント n (8 ビット)、
アドレス (16 ビット、ビッグエンディアン)、レコード型 (8 ビット)、
n バイトのデータ、そこまでのバイト列の合計の最下位バイトの 2 の補数で
表されるチェックサム (8 ビット)、からなる 16 進文字の並びが続きます。
例: :0300300002337A1E
。
このような行を処理するための最初のステップは、四つのフィールドを得るために
16 進データをバイナリに変換して、チェックサムを調べることです。
ここには驚きはありません: 全てをバイナリに変換するための単純な
pack
呼び出しから始めます:
my $binrec = pack( 'H*', substr( $hexrec, 1 ) );
結果のバイト並びははチェックサムを計算するのに最も便利です。
この文字列のバイトの ord
の値を加算するループでプログラムの速度を
落とすようなことをしないでください - unpack
の %
コードは
全てのバイトの 8 ビットの合計を計算するためのもので、これは 0 でなければ
なりません:
die unless unpack( "%8C*", $binrec ) == 0;
最後に、四つのフィールドを取り出しましょう。
ここまでで、最初の三つのフィールドを取り出すのには何の問題もないはずです -
しかし最初のフィールドにあるデータのバイトカウントをデータフィールドの
長さに使うにはどうすればいいでしょう?
ここで x
と X
が助けにやってきて、戻って文字列の 4 番目を
unpack できるようにします。
my( $addr, $type, $data ) = unpack( "x n C X4 C x3 /a", $bin );
x
コードは、まだカウントは必要ではないので 1 バイト飛ばします。
n
コードは 16 ビットビッグエンディアン整数アドレスを取得し、
C
はレコード型を unpack します。
データが始まる位置であるオフセット 4 に来て、カウントが必要です。
X4
は 1 マス目、つまりオフセット 0 のバイトに戻ります。
ここでカウントを取りだして、正確な数のデータを展開するために提供されている
オフセット 4 に移動して、末尾のチェックサムバイトだけを残します。
前のセクションで、数値と文字列を pack する方法を見ました。 もしここに障害がないなら、「C 構造体には他に何もなく、従ってあなたは 既に C 構造体を pack/unpack するための全てを知っています。」 という簡潔な見解と共にこの章をすぐに締めくくることができます。 すみません、そうではありません: どうか読み進めてください。
もし大量の C 構造体を扱う必要があって、全てのテンプレート文字列を手動で
ハックしたくないなら、おそらく一度 CPAN モジュール Convert::Binary::C
を
見たほうが良いでしょう。
C ソースを直接パースできるだけでなく、この章でさらに記述される全ての
雑務に対する組み込みのサポートがあります。
速度とメモリ消費のバランスは、より速く実行できる方に傾いています。 これは C コンパイラが構造体のためにメモリを割り当てる方法に影響します: 偶数、4 の倍数、あるいは 8 の倍数のアドレスにアライメントされていれば、 16 ビットや 32 ビットのオペランドや CPU レジスタへの出し入れが速くなる アーキテクチャでは、C コンパイラは構造体に追加のバイトを入れることで この速度メリットを受けるようにします。 もし C の海岸線を越えないのなら、これがなんらかの面倒を引き起こすことは ありそうにありません (しかし、大きなデータ構造を設計したり、 アーキテクチャ間で移植性のあるコードがほしい場合(そうしたくないですか?)、 気にするべきです。)。
これが pack
と unpack
にどのように影響を与えるかを見るために、
これら 2 つの 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 コンパイラは gappy_t
変数には 12 バイトを割り当てますが、
dense_t
には 8 バイトしか割り当てません。
これをさらに調査した後、余分な 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 は隙間を埋め合わせられないの?」には答えるのが
当然です。
一つのよい理由は、個々の構造体フィールドのレベルでさえ、構造体の
アライメント方法のあらゆる種類の制御方法を許している(非 ANSI の)拡張を
提供しているCコンパイラがあるからです。
そして、もしこれが十分でないなら、埋めるバイト数が次のアイテムの
アライメントだけでは決定されない、union
と呼ばれる
陰険なものがあります。
よし、では困難に耐えましょう。
これは、リストから対応する要素を使わないテンプレートコード x
を
挿入することでアライメントを正しくする一つの方法です:
my $gappy = pack( 'cxs cxxx l!', $c1, $s, $c2, $l );
l
の後の !
に注意してください: long 整数を 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 );
げー、新しいテンプレートコードです - まだ十分ではありませんでした。
しかし @
は次のアイテムのための pack バッファの先頭からのオフセットを
指定できるようにすることで手間を省きます:
これは単に、struct
型とそのフィールド名(C 標準での「メンバ指定子」)を
与えたときに (<stddef.h>
で定義されている)offsetof
マクロが
返す値です。
オフセットを使ったり、隙間を渡すために x
を追加することでは
十分ではありません。
(構造体が変更されたときに何が起こるかを単に想像してみてください。)
本当に必要なものは、「次の N の倍数のバイトになるまでスキップする」と
書く各方法です。
雄弁なテンプレートでは、これは x!N
とできます (ここで N は適切な値
に置き換えられます)。
これは構造体のパッケージ化の次のバージョンです:
my $gappy = pack( 'c x!2 s c x!4 l!', $c1, $s, $c2, $l );
これは確実により良いものですが、未だに全ての整数の長さを知る必要があり、
移植性とはかけ離れています。
例えば、2
の代わりに、「とにかく short の長さ」と書きたいです。
しかし、これは適切な pack コードを大かっこで囲むこと ([s]
) で
可能です。
それで、これはできる限り最良のものです:
my $gappy = pack( 'c x![s] s c x![l!] l!', $c1, $s, $c2, $l );
ここで、異なるバイト順のマシンのためのデータを pack したいとします。 まず、ターゲットマシンでの実際のデータ型の大きさを知る必要があります。 long は 32 ビット幅で short が 16 ビット幅と仮定しましょう。 ここでテンプレートは以下のように書き換えられます:
my $gappy = pack( 'c x![s] s c x![l] l', $c1, $s, $c2, $l );
もしターゲットマシンがリトルエンディアンなら、以下のように書けます:
my $gappy = pack( 'c x![s] s< c x![l] l<', $c1, $s, $c2, $l );
これは short と long のメンバをリトルエンディアンに強制し、もし あまり多くの構造体メンバがない場合は十分です。 しかし、グループにバイト順修飾子を使うことも出来、以下のように書けます:
my $gappy = pack( '( c x![s] s c x![l] l )<', $c1, $s, $c2, $l );
これは以前ほど短くありませんが、ここのテンプレートだけでなく、グループ全体に リトルエンディアンのバイト順を意図していることがより明らかです。 これはまたより読みやすく、管理もより簡単です。
アライメントの捕捉について、十分に説明していないのではないかと 心配しています。 構造体の配列を pack しようとすると、ヒドラはまた別の醜い頭をもたげてきます。
typedef struct { short count; char glyph; } cell_t;
typedef cell_t buffer_t[BUFLEN];
どこに罠があるのでしょう?
最初のフィールド count
の前や、これと次のフィールド glyph
の間に
パッディングは不要です; それならなぜ以下のように簡単に
pack できないのでしょう:
# something goes wrong here: pack( 's!a' x @buffer, map{ ( $_->{count}, $_->{glyph} ) } @buffer );
これは 3*@buffer
バイトに pack しますが、buffer_t
のサイズは
BUFLEN
の 4 倍になるのです!
このお話の教訓は、構造体や配列で必要なアライメントは、それぞれの要素の
最後に パッディングを考慮する必要がある場所で、より高いレベルに
伝播するということです。
従って、正しいテンプレートは:
pack( 's!ax' x @buffer, map{ ( $_->{count}, $_->{glyph} ) } @buffer );
上記のことを全て頭に入れたとしても、ANSI は以下のような場合:
typedef struct { char foo[2]; } foo_t;
サイズは様々であるとしています。
構造のアライメント制約は、それぞれの要素よりも大きいかもしれません。
[そしてもしこれが一般的には何も影響を与えないと考えているなら、
次に見た携帯電話を分解してみてください。
多くは ARM コアを使っていて、ARM 構造体ルールでは
sizeof (foo_t)
== 4 となります]
この章のタイトルは、C の構造体を pack するときに遅かれ早かれ出会うことになる
2 番目の問題を指し示しています。
呼び出そうとしている関数が、例えば、void *
の値を想定している場合、
単純に Perl の変数のリファレンスを使うことは できません。
(確かに値はメモリアドレスですが、値の内容が保持されているアドレスでは
ないからです。)
テンプレートコード P
は、「固定長文字列へのポインタ」を pack することを
約束します。
これが望みのものではないですか?
試してみましょう:
# 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'; 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 の syscall
は、この回りくどい方法でポインタを
構成する必要は ありません 。
単に文字列変数を渡せば、Perl がアドレスを転送します。)
unpack
では P
はどのように動作するのでしょう?
unpack されようとしているバッファにあるポインタを想像します:
もしそれが(賢く undef
値を生成する)ヌルポインタでない場合、開始アドレスを
得ることになります - でも、それで?
Perl はこの「固定長文字列」の長さを知る方法がないので、P
の後ろに
明示的な長さとして実際の大きさを指定する必要があります。
my $mem = "abcdefghijklmn"; print unpack( 'P5', pack( 'P', $mem ) ); # prints "abcde"
結果として、pack
は P
の後の数値や *
を無視します。
ここで P
の動作は見たので、同様に p
を試してみます。
とにかく、なぜポインタを pack するのに 2 番目のテンプレートコードが
必要なのでしょう?
答えは、
unpack
の p
はバッファから取った NUL 終端された文字列がその
アドレスから始まっていることを約束していて、返されるデータアイテムの
長さを暗示しているという単純な事実の後ろに横たわっています:
my $buf = pack( 'p', "abc\x00efhijklmn" ); print unpack( 'p', $buf ); # prints "abc"
それでもこれは混乱しがちです: 長さが文字列の長さを暗示しているので、
p
の後の数値は繰り返し数であって、P
の後のように長さではありません。
$x
が実際に保管されているアドレスを得るために pack(..., $x)
で
P
や p
を使うことは慎重に行われなければなりません。
Perl の内部機構は変数とそのアドレスの関係をとてもプライベートな問題と
考え、私たちがコピーを得たことを実際には気にしません。
従って:
その変数のアドレスのメモリを使い終わる前にスコープから出る(従って
メモリが開放される)ような変数のアドレスを得るために
pack
の p
や P
を使わないでください。
変数の値を変更する Perl 操作にとても注意してください。 例えば、値に何かを追加すると、その保管場所を再配置することになって、 ポインタを誰もいないところにしたままにすることに なるかもしれません。
整数や倍精度実数として保管されている Perl 変数のアドレスを取れるとは
考えないでください!
pack('P', $x)
は、ちょうど $x .= ''
のようなものを書いたのと同様に、
変数の内部表現を文字列に強制します。
しかし、文字列リテラルを P または p で pack することは安全です; なぜなら 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 );
単純なメモリダンプのために、バイト列を 16 進数の組に unpack し、
map
を使って伝統的な表現 - 1 行に 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' );
Simon Cozens と Wolfgang Laun。