次のプログラムを作成してGNU C++コンパイラを使用すると、出力は1
これは、コンパイラーによって実行される回転操作によるものだと思います。
#include <iostream>
int main()
{
int a = 1;
std::cout << (a << 32) << std::endl;
return 0;
}
しかし、論理的には、ビット幅をオーバーフローするとビットが失われると言われているため、出力は0になります。何が起きているのでしょうか?
コードはideoneにあります http://ideone.com/VPTwj 。
これは、Cでの未定義の動作と、IA-32プロセッサ用に生成されたコードにシフトカウントに5ビットマスクが適用されているという事実の組み合わせが原因です。つまり、IA-32プロセッサーでは、シフトカウントの範囲は0-31のみです。 1
FromCプログラミング言語 2
右のオペランドが負の場合、または左の式の型のビット数以上の場合、結果は未定義です。
FromIA-32 Intel Architecture Software Developer's Manual 3
8086はシフト数をマスクしません。ただし、他のすべてのIA-32プロセッサー(Intel 286プロセッサー以降)は、シフトカウントを5ビットにマスクするため、最大カウントは31になります。このマスキングは、すべての動作モード(仮想8086モードを含む)命令の最大実行時間を短縮します。
1 http://codeyarns.com/2004/12/20/c-shift-operator-mayhem/
2 A7.8シフト演算子、付録A.リファレンスマニュアル、Cプログラミング言語
3 SAL/SAR/SHL/SHR –シフト、第4章命令セットリファレンス、IA-32インテルアーキテクチャソフトウェア開発者マニュアル
C++では、型のサイズよりも少ないステップで値をシフトする場合にのみ、シフトは明確に定義されます。 int
が32ビットの場合、0から31ステップまでが明確に定義されます。
それで、これはなぜですか?
シフトを実行する基礎となるハードウェアを見ると、値の下位5ビット(32ビットの場合)だけを見る必要がある場合、検査する必要がある場合よりも少ない論理ゲートを使用して実装できます。値のすべてのビット。
コメントの質問への回答
CおよびC++は、利用可能なハードウェアで可能な限り高速に実行されるように設計されています。現在、生成されたコードは、基になるハードウェアが指定された範囲外の値をどのように処理するかに関係なく、単に「シフト」命令です。言語がシフトの動作方法を指定している場合、生成されたユーザーは、シフトを実行する前にシフト数が範囲内にあることを確認する必要があります。通常、これにより3つの命令(比較、分岐、シフト)が生成されます。 (確かに、この場合、シフトカウントがわかっているので必要ありません。)
C++標準によると、未定義の動作です。
E1 << E2の値は、E1左シフトE2ビット位置です。空のビットはゼロで埋められます。 E1に符号なしの型がある場合、結果の値はE1×2 ^ E2で、結果の型で表現可能な最大値よりも1を法として減じられます。それ以外の場合、E1に符号付きの型と負でない値があり、E1×2 ^ E2が結果の型で表現できる場合、それが結果の値です。 それ以外の場合、動作は未定義です。
Lindydancerおよび6502の回答は、(一部のマシンでは)なぜ1
が印刷されるのかを説明しています(ただし、操作の動作は未定義です)。明確でない場合に備えて、詳細を追加しています。
(私のように)あなたはIntelプロセッサーでプログラムを実行していると思います。 GCCは、シフト操作のために次のアセンブリ命令を生成します。
movl $32, %ecx
sall %cl, %eax
sall
およびその他のシフト操作のトピックについては、 Instruction Set Reference Manual の624ページに次のように記載されています。
8086はシフト数をマスクしません。ただし、他のすべてのIntelアーキテクチャプロセッサ(Intel 286プロセッサ以降)は、シフトカウントを5ビットにマスクするため、最大31カウントになります。このマスキングは、すべての動作モード(virtual-8086モードを含む)命令の最大実行時間。
32の下位5ビットはゼロであるため、1 << 32
は1 << 0
、つまり1
と同等です。
より大きな数を試してみると、
cout << (a << 32) << " " << (a << 33) << " " << (a << 34) << "\n";
1 2 4
を出力します。実際、それが私のマシンで起こっています。
期待しすぎているため、期待どおりに動作しません。
X86の場合、ハードウェアは、カウンターがレジスターのサイズよりも大きい場合のシフト操作を考慮しません(たとえば、 x86リファレンスドキュメント のSHL命令の説明を参照してください)。
C++標準は、生成されたコードがパラメトリックシフトごとに追加のチェックとロジックを追加することを余儀なくされるため、これらの場合に何をすべきかを伝えることで追加コストを課すことを望みませんでした。
この自由度により、コンパイラの実装者は、テストや分岐なしで、アセンブリ命令を1つだけ生成できます。
より「有用」かつ「論理的」なアプローチは、たとえば(x << y)
に相当 (x >> -y)
また、論理的で一貫した動作を持つ高カウンターの処理。
ただし、これにはビットシフトの処理がはるかに遅い必要があったため、ハードウェアが行うことを選択することになり、プログラマはサイドケース用に独自の関数を記述する必要がありました。
これらの場合、異なるハードウェアが異なることを行うことを考えると、標準では、基本的に「奇妙なことをするときは何でもC++を責めないで、それはあなたのせいです」と法的に翻訳されています。
32ビット変数を32ビット以上シフトすることは未定義の動作であり、コンパイラーがデーモンを鼻から飛び出させる可能性があります。
真剣に、ほとんどの場合、出力は0になります(int
が32ビット以下の場合)。これは、1を再びドロップし、0のみが残るまでシフトするためです。しかし、コンパイラーはそれを最適化して、好きなことを行うことができます。
優れたLLVMブログエントリ すべてのCプログラマが未定義の動作について知っておくべきこと を参照してください。これは、すべてのC開発者にとって必読です。
Intを32ビットだけビットシフトしているため、取得します:warning C4293: '<<' : shift count negative or too big, undefined behavior
VSで。これは、整数を超えてシフトしていることを意味し、未定義の動作であるため、答えは何でもかまいません。
以下を試すことができます。これにより、実際には、0
が左にシフトした後、32
として出力されます。
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int a = 1;
a <<= 31;
cout << (a <<= 1);
return 0;
}
私は同じ問題を抱えており、これは私のために働いた:
f =((long long)1 <<(i-1));
ここで、iは32ビットより大きい任意の整数です。シフトが機能するには、1が64ビット整数でなければなりません。