HOWTO · C

C の算術右シフト

C の唯一の >> 演算子が符号なし値をどのようにシフトするか、そして負の符号付き整数の右シフトに移植性がない理由を学びます。

このページの内容

C の右シフト演算子は >> だけです。整数値を、右側のオペランドで指定したビット数だけ右にシフトします。符号なし整数では、空いた上位ビットに 0 が入ります。負の符号付き整数をシフトした結果は C の実装間で移植可能ではないため、>> が常に算術右シフトを行うとは限りません。

C のシフト演算子

C には二項演算子 << と >> があります。左オペランドに整数値、右オペランドにシフト数を指定します。式 value >> count は value を count ビット右にシフトし、式 value << count は左にシフトします。

たとえば value >> 1 は 1 ビット分シフトします。演算の前に両方のオペランドに整数拡張が適用されるため、実際にシフトされる型や値は、幅の狭い整数オブジェクトの宣言時の型と異なる場合があります。

C のシフト演算子の種類

C に算術右シフト演算子と論理右シフト演算子が別々にあるわけではありません。演算子は常に >> で、適用される動作は値の型と値によって決まります。符号なしの値を右シフトすると、空いた上位ビットには 0 が入ります。0 以上の符号付きの値を右シフトした結果は、対応する 2 のべき乗で割った結果と同じです。

負の符号付き整数の右シフトは移植可能ではありません。実装によって動作が異なる場合があるため、符号ビットが複製されることを、移植性のあるコードで頼れる保証として扱わないでください。このため、「算術右シフト」という語は一部の実装で見られる動作を表すものであり、C の別の演算子を意味しません。その動作に依存するプログラムでは、コンパイラとターゲットに関する前提を文書化してください。

次の例を見てみましょう。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <stdio.h>

int main(void) {
    unsigned int value = 15u;
    int signed_value = -15;

    printf("unsigned: %u >> 1 = %u\n", value, value >> 1);
    printf("signed value: %d\n", signed_value);
    return 0;
}
unsigned: 15 >> 1 = 7
signed value: -15

この例の出力欄には unsigned: 15 >> 1 = 7 と signed value: -15 が表示されます。表示ラベルは、コードと同じ英語のままです。符号なしの式 15u >> 1 の結果は、unsigned int の幅が適合するどの実装でも 7 です。プログラムは負の符号付き値を表示しますが、その値を右シフトしないため、負の値を右シフトした場合の結果については示していません。

2 のべき乗による除算との類推には限界があります。この類推は符号なしの値と 0 以上の符号付きの値には有用ですが、負の符号付き整数の右シフトを移植可能にするものではありません。また、シフト数は 0 以上で、整数拡張後の左オペランドの幅より小さくなければなりません。そうでない場合の動作は未定義です。左シフトが常に 2 のべき乗との乗算を意味すると考えないでください。符号付き整数のオーバーフローによって、その演算の動作も未定義になることがあります。

まとめ

C の右シフト演算子は >> の 1 つです。符号なし整数の右シフトでは 0 で埋める動作が定義されていますが、負の符号付き整数をシフトした結果は移植可能ではありません。ビット単位で予測可能な右シフトが必要な場合は符号なし整数を使い、実装要件で明示的に許されていない限り、特定の符号付き整数の動作に依存しないでください。