Diary?

2009-02-14
Sat

(10:51)

とある理由により Python でバイナリデータを弄くり回す必要が出てきて、そういう時には struct モジュールを使うことがあるんだが、こいつにちょっと罠があって軽くハマった。

>>> struct.calcsize('b') # char 1個分のサイズ
1
>>> struct.calcsize('i') # int 1個分のサイズ
4
>>> struct.calcsize('bi') # char と int それぞれ1個ずつのサイズ
8

何勝手にパディングしてんだよ! まあ大体予想は付くけど(どうせ適当なバイト境界にアラインメントしてんだろ)、とりあえず Python の struct モジュールのソース(実体は C)を読んでみるか。ちなみにバージョンは 2.5.4 な。

該当するソースは Modules/_struct.c で、ざっと目を通して目についたのは次の構造体。

typedef struct _formatdef {
    char format;
    Py_ssize_t size;
    Py_ssize_t alignment;
    PyObject* (*unpack)(const char *,
                const struct _formatdef *);
    int (*pack)(char *, PyObject *,
            const struct _formatdef *);
} formatdef;

alignment なんつーもろなメンバがあるからこれでビンゴだろ。それでこの formatdef 構造体はネイティブ、ビッグエンディアン、リトルエンディアンといったケースで場合分けされていて、それらは (native|bigendian|lilendian)_table[] として宣言されている。

static formatdef native_table[] = {
    {'x',   sizeof(char),   0,      NULL},
    {'b',   sizeof(char),   0,      nu_byte,    np_byte},
  :
  :
static formatdef bigendian_table[] = {
    {'x',   1,      0,      NULL},
#ifdef PY_STRUCT_OVERFLOW_MASKING
    /* Native packers do range checking without overflow masking. */
    {'b',   1,      0,      nu_byte,    bp_int},
  :
  :
static formatdef lilendian_table[] = {
    {'x',   1,      0,      NULL},
#ifdef PY_STRUCT_OVERFLOW_MASKING
    /* Native packers do range checking without overflow masking. */
    {'b',   1,      0,      nu_byte,    lp_int},
    {'B',   1,      0,      nu_ubyte,   lp_uint},
  :
  :

それでこの native_table[] で作られてる formatdef 構造体を見ると、そこで int や long にアラインメントが指定されてるんだな。これは他二つには無い。

{'h',   sizeof(short),  SHORT_ALIGN,    nu_short,   np_short},
{'H',   sizeof(short),  SHORT_ALIGN,    nu_ushort,  np_ushort},
{'i',   sizeof(int),    INT_ALIGN,  nu_int,     np_int},
{'I',   sizeof(int),    INT_ALIGN,  nu_uint,    np_uint},

ちなみにこのアラインメントは以下のようなマクロで定義されている。

typedef struct { char c; int x; } st_int;
#define INT_ALIGN (sizeof(st_int) - sizeof(int))

ダミーの char を含む構造体を定義して、それに対してオフセットを計算する、と。そしてこのアラインメントを使ってる関数は次の align 関数だけで、さらに align 関数は prepares_s 関数でしか使われていない。実にわかり易いね。

static int
align(Py_ssize_t size, char c, const formatdef *e)
{
    if (e->format == c) {
        if (e->alignment) {
            size = ((size + e->alignment - 1)
                / e->alignment)
                * e->alignment;
        }
    }
    return size;
}

大体謎は解けたな。先の 'bi' というフォーマットを例に処理を追っていくと、

  1. 'b' はアラインメントの対象ではないのでやる事なし。この時点で全体のサイズは 1 バイト
  2. 'i' はアラインメントが指定されているので、先の 'b' のサイズは ((1 + 4 - 1) / 4) * 4 で 4 バイト
  3. そこに 'i' のサイズである 4 が加算されるので、 'bi' というフォーマットのサイズは 8 バイトになる

という処理になる。ぶっちゃけ当初の予想通りだったけど、確かめてみるに越したことはない。

で、こういう勝手なアラインメントをさせないためには、良きに計らえでフォーマットを指定せずに、明示的に "=(アラインメントなしのネイティブバイトオーダ)", ">(ビッグエンディアン)"などを指定する。

>>> struct.calcsize('>bI')
5

やっぱこういう操作をやる時は、下位層で起こっていることがわかんねーとダメだな。

(19:26)

bk1 の検索システムはちょっと腐ってる。商品を検索した結果が1件だけだったらダイレクトにその商品のページを表示する事自体は別にいいんだが、その時点の URL は検索機能の URL のままで、商品のページの URL を知る方法はページ右下のパンくずリストからたどる他無い(画像の URL から類推できなくもないが)。そしてより目につきやすいページ上部のパンくずリストは「トップ > 商品詳細」という代物で、右下のパンくずリストに気がつかない人も結構いるんじゃないかと思う。というか、俺自身が今日になるまで気がついてなかったんだが。

(20:58)

久々に腹抱えて笑った Web 漫画。不条理というか意味不明というか、カオスな漫画が多いけど、その分ツボにハマったときの爆発力は異常。とりあえず俺のツボに来た奴をいくつかピックアップ。

奥様は魔女
ストレートかつブラックで良い。
トラウマママ
うすた京介っぽい? ウサギ達の表情がなんともいえない。
こぶとりじいさん
綺麗に起承転結になっていて、それでもなお意味不明。何やってんだこのじいさん。
畜産Now!
この内容でこのタイトルというセンスは本当に凄い。
水溶性マン
タイトルで落ちてる。
餅に詰まる
一体何があったんだ。
高橋くん
どう考えても何もかもがおかしい。どういう発想してるんだ。
コーヒーえもん
コーヒーカップの中身を見て盛大に吹いた。のび太の表情もいい味出してる。
ドラムえもん
神としか言いようがない。これは反則だ。

しかしこのサイト、 2006 年からやってたのか。全然知らんかった。

Creative Commons
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。