2013-04-04 5 views
9

これはクエイクIIIアリーナから高速逆平方根の実装です:このポインタキャストは厳密なエイリアシング規則を破棄しますか?

float Q_rsqrt(float number) 
{ 
     long i; 
     float x2, y; 
     const float threehalfs = 1.5F; 

     x2 = number * 0.5F; 
     y = number; 
     i = * (long *) &y;      // evil floating point bit level hacking 
     i = 0x5f3759df - (i >> 1);    // what? 
     y = * (float *) &i; 
     y = y * (threehalfs - (x2 * y * y)); // 1st iteration 
//  y = y * (threehalfs - (x2 * y * y)); // 2nd iteration, this can be removed 

     return y; 
} 

私はlong int型iフロートyの(long *にキャスト)アドレスに間接参照値をとることに気づきました。次にコードはiの参照を、のyにあるアドレス(キャストはfloat *)に格納します。

iyと同じタイプではないため、厳密なエイリアシングルールが破られますか?

値が逆参照されていて、がコピーされていると思われます。;操作はオリジナルではなくコピーで実行されます。

+0

私はそれが少なくともsizeof(long)!= sizeof(float)と同じくらい長いと思います。 –

+1

'sizeof'はエイリアシング違反かどうかは関係ありません。 –

+0

書かれているコードは 'float'のように' long'が32ビットの値であると仮定しています。コンパイラが何が起こっているのかを「見る」ことができるので、私は禁止されていますが、ポインタのキャストは面倒なようですが[ポインタがどこかに格納され、後で驚くような方法で使用されるシナリオとは異なります。私はエイリアシングの問題は、とにかくうまくいくケースでは、コード生成なしで、 '(unsigned char *)'への中間のキャストで処理できると思います。私自身の傾向として、代わりに 'union'を使うことがあります。まだ移植可能ではありませんが、コンパイラーは、組合がエイリアスになることを期待するべきです。 – supercat

答えて

6

はい、このコードはひどく壊れており、未定義の動作を呼び出します。具体的には、次の2行に気づく:オブジェクト*(long *)&ylongを入力しているので

y = number; 
    i = * (long *) &y;      // evil floating point bit level hacking 

を、コンパイラ型floatのオブジェクトをエイリアスすることができないことを想定して自由です。従って、コンパイラは、これら2つの演算を互いに並べ替えることができる。

これを修正するには、共用体を使用する必要があります。

1

i = * (long *) &y;

これは、エイリアシング規則を破るため、未定義の動作を呼び出します。

オブジェクトyは、floatと異なるタイプ、またはcharの符号付き/符号なしバリアントとアクセスしています。

y = * (float *) &i;

もエイリアシング規則を破る上記のこの文。

4

はい、エイリアシングルールが破損します。現代のCで

、あなたがi = * (long *) &y;を変更することができます:あなたが使用しているCの実装では、longfloatは適してい、という保証を持って提供

y = (union { long l; float f; }) {i} .f; 

i = (union { float f; long l; }) {y} .l; 

y = * (float *) &i;サイズと表現を定義すると、その動作はC標準によって定義されます。あるタイプのオブジェクトのバイトは、他のタイプとして再解釈されます。

+1

ちなみに、ここでは 'long'は間違った型です。これは 'uint32_t'でなければなりません。また、ILP32/LP64モデルを想定したい場合は、' int'または 'unsigned'でもかまいません。 Windowsを除く現実世界の64ビットターゲットでは、「long」は間違いなく壊れています。 –

+0

@R .: 'longが64ビットであることが制約に違反しています。 'C言語の実装で' long'と 'float'が適切なサイズと表現を持つことを保証しています。 –

+0

私は同意します。私はあなたの答えではなく、引用された元のコードの悪さについてコメントしていました。ごめんなさい。 –

3

はい、エイリアシングルールが破損しています。

i = * (long *) &y;のようなもののためにきれいな修正は、このようになります:

memcpy(&i, &y, sizeof(i)); // assuming sizeof(i) == sizeof(y) 

それは、アライメントやエイリアシングの問題を回避します。また、最適化を有効にすると、通常はmemcpy()への呼び出しをほんの数の命令に置き換える必要があります。

他の方法と同様に、この方法ではトラップ表現に関連する問題は修正されません。しかし、ほとんどのプラットフォームでは、整数型のトラップ表現はありません。浮動小数点フォーマットを知っていれば、浮動小数点フォーマットのトラップ表現があればそれを避けることができます。

+0

この問題は、Q_rsqrtが速い平方根であると考えられているため、memcpyへの呼び出しが望ましくないことがあります。 – RunHolt

+0

@RunHolt今日のコンパイラは、 'memcpy()'のようなものをインライン展開するのに問題はありません。しかし、私はすでにそれを答えて言いました。 –

+0

@AlexeyFrunze:コンパイラがロードとストアの命令に 'memcpy'を最適化できたとしても、単純な操作をしたいコードはオプティマイザによって機能が破壊されるのを避けるために、より複雑な(ソースコードでは読みにくい)操作を要求し、オプティマイザが最適化していないコンパイラが作成したコードと同程度のコードに変換することを期待します。 – supercat

関連する問題