Cプログラムの2つのパラドックス

Cでプログラミングアルゴリズムをプログラミングするときに直面しなければならなかった2つの奇妙な点についてお話しします。



そのため、一部のプログラマーにとって最初の予期しない動作です。 ここに小さなプログラムがあります。



#include <stdio.h> int main() { unsigned char a = 1, b; b = ~a >> 1; printf("%u\n", b); return 0; }
      
      







分析しましょう。 ビット単位の演算は、バイトaの各ビットの状態を反転させます。この単位では、ユニットは最初に書き込まれました。 結果は11111110b 、つまり254になります。 このバイトを1ビットだけ右にシフトすると、 127になります。 ただし、たとえば、 gccコンパイラによって指定されたコードは、 255をコンソールに出力します!?



最初は、問題は優先事項だと思っていました。コンパイラが「刈る」操作に優先権を持っているとしたらどうでしょうか。 つまり、最初にシフトが行われたように、次に-反転(そしてそれは論理的です...)。 それで、取引は何ですか?



いくつかの熟考の後、バイトを反転するとワード(まあ、またはダブルワード)に削減され、反転ワードがシフトされるという別の仮説が浮かびました。 ここから255が生成されます- ワードゼロの最上位ビット、それらを反転させて、ユニットがあります。 次に、1ビット右にワードシフトすると、その最下位バイトにはすべてのビットに単位が含まれます。



これは、次のコードによって確認されます。



 #include <stdio.h> int main() { unsigned char a = 1, b; b = (unsigned char)~a >> 1; printf("%u\n", b); return 0; }
      
      





これで正しい結果が得られました。 しかし、 gccが提供するELFファイルを分解することで、ようやくこれを確信しました。 アセンブラーコードの断片を示します。



 mov [ebp+var_6], 1 movzx eax, [ebp+var_6] not eax sar eax, 1 mov [ebp+var_5], al
      
      





まず、スタックを介して、ユニットは32ビットeaxレジスタに入ります。 次に反転し、シフトします。 結果は、 axレジスタの若い部分( alレジスタ)から取得されます。 これは私の仮説を正当化します-ダブルワードをシフトするときに、目的のバイトの後ろにあったユニットがそれに陥りました。



後で判明したように、この状況はInteger Promotionと呼ばれ、 C99標準の 6.3.1.1節で説明されています。 こちらからダウンロードできますwww.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf



一部のプログラマーの2番目の予期しない動作は、実数によるものです。 次のコードがあります。



 #include <stdio.h> int main() { float a = 1.005, b = 1000; int c = a*b; printf("%d\n", c); return 0; }
      
      





gcc 4.1.1でコンパイルすると、 1004になります。 繰り返しますが、奇妙な結果はどこから来るのでしょうか? それでも



 int c = (float)(a*b);
      
      





また、正しい結果が得られません。



C89標準に従って登山したところ、実数を扱う方法について何も規制していなかったことが判明しました。 結局のところ、 SSE拡張機能が登場したとき、コンパイラーは複雑な方法でカウントを開始しました-カウントが速くなるにつれて、 FPU上のもの、 SSE上のもの。 新しいC99標準にはある程度の確実性があります。 コンパイラーは、マクロ値FLT_EVAL_METHOD (ヘッダーファイルfloat.h )を0、1、2に設定して 、考慮します。 したがって、 0-すべてが書き込まれたとおりにカウントされます。 1 - floatは実際にはdoubleとしてカウントされ、その後floatに変換されます。 2-すべてをlong doubleでカウントし、それぞれ計算の最後にfloatまたはdoubleに変換します。



ここで、プログラムを必要な数だけカウントするには、それを収集する必要があります



 gcc proga.c -msse
      
      





その後のみ、コンソールで1005を取得しました。 私のバージョンのgccコンパイラーはマクロFLT_EVAL_METHODをサポートしていないことが判明しました。 ちなみに、 ダブル gccを使用すると、コード1004が出力されます。 Intel C 9.0のみがdoubleで通常の​​コードを作成しましたが、私が書いたとき



 int c = (float)(a*b);
      
      





(ここabはすでにdouble型です)。 型キャストを行わない場合、そこのコードも1004になります。



All Articles