2012年06月19日

C言語のあいまいさ

もうすぐ台風台風4号が上陸してきそうな中、このブログを書いているコンドウです。
ご無沙汰しております。

台風台風が間もなく近づいてくるという事で会社も早めの電車帰宅を促しておりますが、
そろそろ撤退しなければと考えていますダッシュ(走り出すさま)

今日はいいネタが思い浮かばなかったのでC言語のあいまいさについて
お話しようかと思います。

C言語(C++含む)を何年も実務や趣味でやっていると「あれexclamation&question」って思う挙動に
出くわす事ってないですか? 私はよくあります。
例えば、前の開発の事例で説明しますと、


unsigned int getBit(unsigned int& data,unsigned int shift)
{
unsigned int result = data & ((1<<shift)-1);
data>>=shift;
return result;
}


上の関数は引数dataから指定したbit分の数値を返すbit圧縮の展開などで
使われる関数ですが、これまずいところがあります。お判りでしょうか?

もう1つの例です。以前ブログでも紹介しました浮動小数点で紹介したプログラムです。


#define FRAC_LEN 23

union FLOATING_VALUE{
float FloatValue;
struct {
unsigned int Fraction:FRAC_LEN;
unsigned int Exp:31-FRAC_LEN;
unsigned int Sign:1;
}s;
};


これもよく見るとまずい使い方をしています。


ではまずい箇所を説明します。最初の関数は

data>>=shift;

がまずい部分です。int型の幅は処理系依存になるのですがここでは32bitとして考えます。
もし、shiftが32以上だった場合、dataの値が処理系によって不定になるのです。
なぜかというとC言語の定義に無いコーディングとなるからです。C言語で保障されている
シフトはその変数の幅未満までで、幅以上のシフトは言語的に動作不定となります。
実際、上記のコードはPowerPCやMIPS系のプロセッサでは問題無かったのですが、x86系は
異なった結果になりました。x86の場合はシフト命令はカウンタ値が5bitでマスクされてから
処理されるので例えばshift=32だったら、shift=0になって処理してしまうので期待した結果
になりませんでした。(data>>=shiftより、1行上の処理も当然まずいですが。。)
シフト処理はかなり未定義なところが多く、右シフトの動作は
算術シフト(空いたbitは最上位bitで埋まる)論理シフト(空いたbitは0で埋まる)どちらになる
かはC言語の規格には明確に定義されていません。処理系に依存するという事です。


もう1つの例のまずい箇所は色々あります。1番まずいところは「ビットフィールドの値は
何か変数と共有してはいけない」
というところです。では何がいけないというと

・float FloatValueはどのようにメモリに格納されるのか(ビッグエンディアン/リトルエンディアン)
・ビットフィールドが上位bitから割り当てられるのか、下位bitから割り当てられるのか

ビットフィールドのビットが上位か下位かどちらか割り当てるのかは処理系依存しています。
私の経験上からはビッグエンディアンの処理系では上位から、リトルエンディアンの処理系では
下位から割り当てられているようですが、これも確実ではありません。
よって浮動小数点のブログでは注意書きも入れていたかと思いますが、Windows上(x86系)での
動作に限られます。ビットの順序を重要視するようなコードの場合は面倒でもシフト演算子を
使って自作する事が賢明でしょう。

他にもネットでC言語の未定義動作を検索してみるといくつか知る事ができます。

その点、後発のJavaは未定義部分がほぼ無いといわれています。即ち、処理系に依存するような
コードにはならないという事ですね。どちらがいいか結論付けるのは難しいですがプログラマ
としては処理系によって挙動が変わるのは移植の観点からもあまり好ましくは無いでしょう。

ただゲーム開発は未だC系が主流なので今回の話を頭の片隅にでも置いておければと思います。


posted by 管理人 at 15:34 | プログラミング