yohhoyの日記

技術的メモをしていきたい日記

struct.pack/unpack関数フォーマットにはメモリレイアウトを指定する

Python言語のstructモジュールでは、フォーマット指定先頭文字(@/=/<>/!)にてメモリレイアウト指定を明示すべき。

パック(pack)/アンパック(unpack)フォーマット文字列の省略時デフォルトのメモリレイアウトは、Pythonプログラマの期待に反する可能性が高い。

By default, C types are represented in the machine’s native format and byte order, and properly aligned by skipping pad bytes if necessary (according to the rules used by the C compiler). This behavior is chosen so that the bytes of a packed struct correspond exactly to the memory layout of the corresponding C struct. Whether to use native byte ordering and padding or standard formats depends on the application.

Alternatively, the first character of the format string can be used to indicate the byte order, size and alignment of the packed data, according to the following table:

Character Byte order Size Alignment
@ native native native
= native standard none
< little-endian standard none
> big-endian standard none
! network (= big-endian) standard none

If the first character is not one of these, '@' is assumed.

Byte Order, Size, and Alignment

デフォルト動作(@相当)ではC/C++コンパイラの構造体メモリレイアウトに従うため、型指定子の配置順によって意図しないパディング挿入が行われるケースがある。64bitプロセッサ/Native Endian=Little Endian環境での実行結果。

import struct
from binascii import b2a_hex 
printhex = lambda s: print(b2a_hex(s, ' '))

a, b = 0x12345678, 0x123456789abcdef0

printhex(struct.pack('@Iq', a, b))
# b'78 56 34 12 00 00 00 00 f0 de bc 9a 78 56 34 12'
printhex(struct.pack('=Iq', a, b))
# b'78 56 34 12 f0 de bc 9a 78 56 34 12'
printhex(struct.pack('<Iq', a, b))
# b'78 56 34 12 f0 de bc 9a 78 56 34 12'
printhex(struct.pack('>Iq', a, b))
# b'12 34 56 78 12 34 56 78 9a bc de f0'

printhex(struct.pack('@qI', b, a))
# b'f0 de bc 9a 78 56 34 12 78 56 34 12'
printhex(struct.pack('=qI', b, a))
# b'f0 de bc 9a 78 56 34 12 78 56 34 12'
printhex(struct.pack('<qI', b, a))
# b'f0 de bc 9a 78 56 34 12 78 56 34 12'
printhex(struct.pack('>qI', b, a))
# b'12 34 56 78 9a bc de f0 12 34 56 78'

@指定メモリレイアウトの結果は、一般的なC/C++コンパイラの構造体メモリレイアウトと整合する。*1

#include <stdint.h>

struct S1 {
  uint32_t m1;  // +0: 4byte
  // (padded: 4byte)
  uint64_t m2;  // +8: 8byte
};

struct S2 {
  uint64_t m1;  // +0: 8byte
  uint32_t m2;  // +8: 4byte
};

*1:構造体 S2 ではメンバ変数 m2 に後続して4byteパディングが挿入されるが、structモジュールは末尾パディングを自動挿入しない。