2014年5月11日日曜日

CPU-architecture, C++, memory alignment: この reinterpret_cast はCPU依存でC++言語レベルでは未定義挙動なのでした。


2つ前の記事で投稿した件、Emscriptenで現在モデレーターのような役割で世話を焼いてくれている@jujさんから丁寧なreplyを頂いて気が付きました。
std::string s( { char(0xdb), char(0x0f), char(0x49), char(0x40) } );
この様に定義した std::string の s を、
std::cout << "s: " << *reinterpret_cast<const float*>(s.data()) << "\n";
この様に reinterpret_cast で再解釈して float 値として読み出す。 これはx86、x86_64の世界では問題なく出来てしまう事。

だけど、これを行う為にはCPUというかOS含めた対象のプラットフォームの float のメモリーからの load 命令が char* 同様に任意のメモリーアライメントでの読み出しに対応していないと期待通りの動作にならない。そのため、少なくともC++言語レベルではこれが期待通りの挙動を示すかどうかは「未定義動作」( undefined behavior )となる。

例えば、 ARM 系のCPUではアラインされていない float の読み出しには注意が必要で、ARMv11とARM Cortex-Aを覗くARM系のCPUでは、上記のコードは期待通りに float 値を laod できないし、わりと古いCPUだけど ARMv5 系では例外になるみたい。

  参考: ARM unaligned data access and floating point in Linux

Emscripten処理系でも最初に挙げたような float の読み出し処理はサポートしていなくて、それで予想外の値が帰って来たのでした。

私は今までx86系のCPUでしかネイティブと戦って来なかった。手打ちでのMMXからSSE3やらの最適化でメモリーアライメントに注意したり、独自のアロケーターを実装したりもしてきたから、CPU命令と load/store とメモリーアライメントについてのなんとなくの知識はあった。でも、最初のコードが期待通りに load/store できるのが特殊なx86系に限った事で、一般にはそれが未定義挙動だって知らなかった。

でも、おかげで気付けました(╹◡╹)
EmscriptenのIssueで丁寧なreplyをくれた@jujさんありがとう。

0 件のコメント:

コメントを投稿