ポインタのキャストでエンディアンを意識しないとバグる例
常識だと思っていたことがあまり知られていなかったっぽいのでメモを兼ねて共有する。
問題設定
void event1(int32_t* param) { uint8_t data = (uint8_t)*param; printf("%"PRId8"\n", data); // PRId8:uint8_tのフォーマット指定子 } void event2(int32_t* param) { uint8_t data = *(uint8_t*)param; printf("%"PRId8"\n", data); }
上のようなint32_t*
を引数に取るイベント関数を考える。この関数たちにuint8_t
の値を渡したい場合、それぞれ次のように書く。
// 渡したいデータ uint8_t data = 96; int32_t param1 = (int32_t)data; event1(¶m1); // 検証のために適当に値を埋めておく int32_t param2 = 0xdeadbeef; *( (uint8_t*)¶m2 ) = data; event2(¶m2);
読み出し方に応じて値の格納の方法も変えなければならない。
Little Endianの環境では、int32_t*
の指す先に下位bitが詰まっているので、どちらの方法で読み書きしても問題なく動く。一方、Big Endianの環境ではint32_t*
には上位の方の値が格納されているので、書き込み方によってレジスタの中身が変わってしまう。
実験
Big Endianだと本当にバグるのか実験してみた。
Big Endianで動くコンピュータが手元にないので、qemuで仮想環境を作った。wslで以下のコマンドを打てば、Windowsでも簡単にBig Endian環境でテストができる。
sudo apt-get install gcc-multilib-mips-linux-gnu gcc-mips-linux-gnu qemu-user mips-linux-gnu-gcc test.c -o test -static qemu-mips ./test
この環境で
event1(¶m1); event2(¶m2); event1(¶m2); event2(¶m1);
を実行すると、結果は
96 96 239 0
となった。確かに、変数への格納と取り出しはセットにしないとバグってしまった。