2011年9月14日水曜日

g++ bit-fieldsの謎

C++でbit field使ってるコードでハマった。
擬似的に書くと以下のようなクラス。

class A {
  signed a: 15;
  signed b: 15;
  bool c: 1;
  bool d: 1;
  void *e;
};

んで、Vector<A>を比較するのに、性能を気にしてsizeof(A)*lengthでmemcmp。
x86やARMでは問題ないんだけど、x86_64になると問題発生。
実質12Bのペイロードに対して、sizeof(A)が16。
全フィールドを初期化しても未初期化領域が必ず残ってしまうという罠。
ポインタがいるから8Bに揃えてるんだろな、と思ってポインタを頭に持って行っても結果はかわらず。
x86_64 ABI的には構造体は8Bアライメントなのか。。。
ということで、memcmpでは不一致が発生してしまう。
ちなみに32bit環境ではsizeof(A)は8でびっしり詰まってる感じ。

その後、色々試しているうちに気づいたんだけど、
bit-fieldが15,15,1,1の組み合わせだと詰め込んで32bitに納めてくれるんだけど、
31,31,1,1の組み合わせだと64bitに詰め込んでくれない。
けど、31,1,31,1の順に並び替えれば64bitに納めてくれる。。。どんな規約なんだ?

・・・と、そうこうしてたら@nminoru氏のABI仕様の指摘。
出てきた順にLSBから32bit単位で詰めてく模様。
はみ出そうになったらパディングして次のアライメントから。
なので、15,15,1,1の組み合わせは(15+1)x2になってるわけじゃなく、
[14:0], [29:15], [30] [31]というマップになるようだ。
だから、31,31,1,1の場合は、31,31+1,1となって、31,1,31,1の場合は(31+1)x2。
うん、納得だ。__attribute__((packed))がbit-fieldに対しても効果があることも、
32bit単位のアライメントを無効化してくれると思えば納得なのであった。

という事で、もっと仕様をちゃんと読め的な、いつもどおりの教訓を胸に抱きつつ、次号にコンティニュー。

参考)
IA-32 ABI : http://www.sco.com/developers/devspecs/abi386-4.pdf#page=33
x86_64 ABI: http://www.x86-64.org/documentation/abi.pdf#page=14

2 件のコメント:

  1. Aの配列は8バイトアラインでないとポインタの場所がずれるからでは? >64bit

    返信削除
  2. Yes。
    引っかかったのは、31-31-1-1-64でbitが並ぶケース。64bit alignだけ考えたら16Bなんだけど、bif fieldが32bit境界をまたげないという別の制限から24Bになってしまうのです。
    この点が想像の範疇に無かったので悩みました。

    返信削除