C言語基礎|シフト演算

ビットをずらすだけで、掛け算・割り算みたいに動く!シフト演算で“2進数の感覚”を身につけよう。

シフト演算ってなに?

シフト演算は、整数の中に並んでいる 0 と 1(ビット列)を、左または右にまとめて「ずらす」操作です。
C言語では、次の2つが主役になります。

  • 左シフト:<<
  • 右シフト:>>

ビットをずらすと、2進数の桁が動くので、(条件がそろえば)値が 2倍 / 1/2 みたいに変化します。
ただし、オーバーフローや符号付き負数の扱いなど、落とし穴もあるので、そこも一緒に丁寧に押さえていきましょう。

シフト演算子(<< と >>)の基本

ビット単位のシフト演算子

演算子書式何をする?空いたビット
<<a << ba を b ビット左にずらす右側(下位側)に 0 を入れる
>>a >> ba を b ビット右にずらす左側(上位側)がどう埋まるかは状況次第

この表の説明

  • << は「左へ移動して、空いたところは 0 で埋める」と覚えてOKです(非負の範囲で特に分かりやすい)。
  • は、unsigned(符号無し)なら左側は 0 が入るのが基本イメージで、非負なら割り算っぽく動きます。
  • どちらも 整数型にしか使えません(double などはエラー)。

シフトすると何が起きる?

本文の Fig.7-17 を、見やすい図として整理します(例は非負の整数)。

図:左シフトと右シフト(非負の例)

この図の説明

  • 左シフトは「右側に 0 を足す」感じになり、値が大きくなりやすいです。
  • 右シフトは「下位ビットが落ちる」ので、値が小さくなりやすいです。
  • ただし、左に押し出されたビットは消えるので、情報が失われます(これがオーバーフローや桁あふれの感覚につながります)。

値としてはどう変化する?(非負・unsignedでの目安)

非負の整数でのシフトと値の関係(目安)

ざっくりした意味条件
x << nx × 2nオーバーフローしない範囲で
x >> nx ÷ 2n の整数部分x が非負、特に unsigned だと分かりやすい

この表の説明
2進数は各桁が 20, 21, 22… の重みを持っています。
だから、1ビット左にずらすと重みが1段上がって 2倍、右にずらすと 1/2 っぽくなる、というわけです。


重要:符号付きの負数をシフトしないほうがいい理由

本文にもある通り、負数の右シフトの結果は処理系に依存しやすいです。
多くの処理系では次のどちらかになります。

右シフトの2つの考え方(代表例)

名前何が起きる?目的
論理シフト左側を 0 で埋めるビット列として扱いたい
算術シフト左側を符号ビットで埋める符号付きの割り算っぽくしたい

この表の説明

  • 同じ x >> n でも、負数だと結果が変わる可能性があり、移植性が下がります。
  • なので、教材としては 負数のシフトはやらない方針でOKです。
  • ビット操作をしたいときは、まず unsigned を使うのが安全です。

登場する命令(演算子)の書式と「何をするか」

シフト演算子

演算子書式何をする命令?(役割)
<<a << ba の全ビットを b だけ左に移動して新しい値を作る。
>>a >> ba の全ビットを b だけ右に移動して新しい値を作る。

代入を伴うシフト(よく使う書き方)

本文には直接は出ていませんが、同系列としてセットで覚えると便利です。

演算子書式意味
<<=a <<= ba = a << b と同じ
>>=a >>= ba = a >> b と同じ

この表の説明

  • 繰り返しシフトする処理(ビット走査、分解、圧縮など)で大活躍します。

サンプルプログラム

2の累乗マスクを作ってビット位置を示すプログラムです。

  • 入力:ビット位置 pos(0〜)
  • 出力:1U << pos の値とビット列
  • ついでに右シフトで元に戻す様子も見せます

プロジェクト名:chap7-17-1 ソースファイル名:chap7-17-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// シフト演算で「ビットマスク」を作って確認する例
#include <stdio.h>

int count_bits(unsigned x)
{
    int bits = 0;
    while (x) {
        bits++;
        x >>= 1;
    }
    return bits;
}

int int_bits(void)
{
    return count_bits(~0U);  // unsigned の全ビットが1の値を数える
}

void print_bits(unsigned x)
{
    for (int i = int_bits() - 1; i >= 0; i--) {
        putchar(((x >> i) & 1U) ? '1' : '0');
    }
}

int main(void)
{
    unsigned pos;

    printf("ビット位置を入力してください(0以上)。\n");
    printf("pos = ");
    scanf("%u", &pos);

    unsigned mask = 1U << pos;

    putchar('\n');
    printf("作ったマスク値      = %u\n", mask);
    printf("マスクのビット列    = ");
    print_bits(mask);
    putchar('\n');

    printf("右に戻す(mask >> pos)= ");
    print_bits(mask >> pos);
    putchar('\n');

    printf("\nメモ:1U を pos だけ左にずらすと、その位置だけ1になります。\n");

    return 0;
}

このプログラムで学べること(説明)

  • 1U << pos は、pos番目のビットだけが1の値(マスク)を作る定番テクニックです。
  • mask >> pos をすると、(posが範囲内なら)最下位ビットに 1 が降りてきて、結果は 1 になりやすいです。
  • print_bits の中では x >> i を使って i番目のビットを取り出すことをしています。

よくある注意点(超大事)

シフト演算の注意ポイント

注意点何が起きる?対策
シフト量が大きすぎるビット幅以上のシフトは危険(未定義になり得る)0〜(ビット数-1)に収める
左シフトのオーバーフローはみ出したビットが消える範囲内で使う/unsigned中心で扱う
負数の右シフト結果が処理系依存になりやすい負数はシフトしない、unsignedを使う

この表の説明
「動くはず」という感覚で書くと危ない場所なので、学習段階から安全なルールで身につけるのがおすすめです。

まとめ:シフトは“ビットの引っ越し”

  • << は左へ引っ越し(右に 0 が入る、値は増えやすい)
  • は右へ引っ越し(下位が落ちる、値は減りやすい)
  • ビット操作は unsigned を基本にすると安全
  • 1U << pos はマスク作りの定番