C言語のきほん|ビットシフトとシフト演算子の基本

ビットをずらすしくみがわかると、2倍・半分の計算もビット操作もすっきり見えてくる。

C言語では、整数を 0 と 1 の並びとして扱えることが大きな特徴のひとつです。
前回学んだ AND、OR、XOR、NOT のようなビット演算子は、そのビット列を直接操作するための道具でした。

そして、ビット操作の中でも特に重要なのが シフト演算子 です。
シフト演算子は、整数のビット列全体を左や右にずらすための演算子で、ビット単位の移動を簡単に表現できます。

一見すると、ただビットを横に動かしているだけのように見えるかもしれません。
でも実際には、左にずらすと値が 2倍、4倍、8倍のように増えていき、右にずらすと 2分の1、4分の1、8分の1のように小さくなることがあります。
この性質は、ビット列と 2進数の仕組みがそのまま反映されたものです。

シフト演算子は、単に計算を速くするためだけのものではありません。
特定のビットを取り出したり、フラグを整えたり、通信データを分解したり、ハードウェアの状態を調べたりと、低レベルな処理でとてもよく使われます。
特にビットマスクと組み合わせると、かなり細かいビット操作ができるようになります。

ただし、シフト演算には注意点もあります。
特に、符号付き整数の右シフトや、あふれを伴う左シフトは、初心者がつまずきやすいポイントです。
そのため、まずは 符号なし整数で安全に考える ことがとても大切です。

ここでは、左シフトと右シフトの基本的な意味、数値との関係、符号ありと符号なしでの違い、そしてシフトとビット演算を組み合わせたビット判定まで、順番にやさしく整理していきましょう。

シフト演算子とは何か

シフト演算子は、整数のビットを左または右にずらす演算子です。
C言語では、次の2つがあります。

演算子演算意味
<<左シフトa << 4a を 4ビット左へずらす
>>右シフトa >> 2a を 2ビット右へずらす

また、複合代入演算子として使うこともできます。

書き方意味
a <<= 2a = a << 2
a >>= 3a = a >> 3

このように書くと、変数の値をそのまま更新できるので便利です。

左シフトとは何か

左シフトは、x << n のように書き、x のビット列を左に n 個ずらします。

左シフトでは、次のような動きになります。

  • 右側に空いたビットには 0 が入る
  • 左側からあふれたビットは捨てられる

つまり、ビット列全体が左に移動して、右端に 0 が補われる形です。

左シフトの基本イメージ

たとえば、8ビットで 00011001 という値があったとします。
これを2ビット左にずらすと、こうなります。

元の値      00011001
左へ2ビット 01100100

左に移動したぶん、右側に 0 が入っています。
左端からはみ出したビットがあれば、それは捨てられます。

左シフトと 2の累乗倍の関係

左シフトは、あふれが起きなければ、数値を 2ⁿ 倍するのと同じ意味になります。

たとえば、

  • 1ビット左シフト → 2倍
  • 2ビット左シフト → 4倍
  • 3ビット左シフト → 8倍

となります。

例として 100 を左シフトしてみる

100 を 2ビット左シフトしてみます。

uint16_t x = 100;
x = x << 2;

このとき、結果は 400 になります。

なぜそうなるのかというと、

100 × 2² = 100 × 4 = 400

だからです。

2進数で考えても確認できます。
100 は 16ビットで書くと次のようになります。

00000000 01100100

これを2ビット左シフトすると、

00000001 10010000

になります。
これは 10進数で 400 です。

左シフトで注意したいこと

左シフトは便利ですが、左側からあふれたビットは捨てられるので注意が必要です。
つまり、常に安全に「2倍になる」とは限りません。

たとえば、型の大きさを超えるほど左にずらすと、上位ビットが失われてしまいます。
その結果、本来の値とは違う結果になることがあります。

そのため、左シフトを使うときは、

  • 型のビット幅を意識する
  • 値があふれない範囲で使う
  • できるだけ符号なし整数で考える

という点が大切です。

右シフトとは何か

右シフトは、x >> n のように書き、x のビット列を右に n 個ずらします。

右シフトでは、次のような動きになります。

  • 右側からあふれたビットは捨てられる
  • 左側に空いたビットには、型に応じた値が入る

ここで少し注意が必要です。
左側に何が入るかは、符号なし整数か符号付き整数かで扱いが変わります。

符号なし整数の右シフト

符号なし整数では、右シフトしたとき、左側の空いたビットは 0 で埋められます。
これを 論理シフト と呼びます。

たとえば、100 を 2ビット右シフトするとします。

uint16_t x = 100;
x = x >> 2;

この結果は 25 になります。

なぜなら、

100 ÷ 2² = 100 ÷ 4 = 25

だからです。

2進数で見ると、100 はこうです。

00000000 01100100

これを2ビット右にずらすと、

00000000 00011001

になります。
これは 10進数で 25 です。

つまり、符号なし整数の右シフトは、あふれがなければ 2ⁿで割る のと同じように考えられます。

符号付き整数の右シフトは注意が必要

ここはとても大切なポイントです。
符号付き整数の右シフトは、処理系によって動作が異なることがあります。

符号付き整数では、右シフトしたとき、左側に何が入るかが環境によって変わることがあります。

場合左側に入るもの
算術シフト符号ビットが入る
論理シフト0 が入る

多くの環境では、符号付き整数の右シフトは 算術シフト になります。
この場合、負の数を右シフトすると、左側に 1 が入って、符号が保たれます。

たとえば、-100 を 2ビット右シフトしたとき、算術シフトを行う環境では -25 になることがあります。

int16_t x = -100;
x = x >> 2;

実行結果の例としては、

-25

になります。

ただし、これはすべての環境で必ず同じとは限りません。
そのため、負の数のシフトは処理系依存 と考えておくことが大切です。

算術シフトのイメージ

たとえば、16ビットで -100 を表したビット列を考えると、算術シフトでは左側に符号ビットが補われます。
その結果、負の数としての意味を保ちながら右にずれていきます。

負の整数の右シフトの例

シフト後の例
-100-25

このように、算術シフトでは、負の値を保ったまま 2ⁿ で割ったような結果になることがあります。
ただし、これは「よくある挙動」であって、常に保証されるわけではありません。

だからこそ符号なし整数でシフトするのが安全

シフト演算を安全に考えるなら、まずは符号なし整数を使うのが基本です。
符号なし整数なら、右シフトは 0 埋めになるので、動作が理解しやすく、環境差も避けやすくなります。

そのため、ビット操作そのものが目的なら、uint16_t や uint32_t のような符号なし固定幅整数型を使うのがとてもおすすめです。

サンプルプログラムで確認する

左シフトと右シフトの結果を確認しやすいシンプルなプログラム例です。

ファイル名:16_7_1.c

#include <stdio.h>
#include <stdint.h>

int main(void)
{
    /* シフトの確認用の値を用意する */
    uint16_t left_value = 100;
    uint16_t right_value = 100;

    printf("シフト演算の結果を確認してみましょう。\n\n");

    printf("元の値 %hu を2ビット左シフトすると %hu です。\n",
           left_value, (uint16_t)(left_value << 2));

    printf("元の値 %hu を2ビット右シフトすると %hu です。\n",
           right_value, (uint16_t)(right_value >> 2));

    return 0;
}

このサンプルプログラムでわかること

このプログラムでは、100 を左に2ビット、右に2ビットずらしています。
その結果、

  • 左シフトでは 400
  • 右シフトでは 25

になることが確認できます。

これによって、

  • 左シフトは 2² 倍
  • 右シフトは 2² で割る

という関係が、具体的な数字としてつかみやすくなります。

入力処理がないぶん、演算そのものの意味に集中しやすいので、最初の確認用としてとても使いやすい例です。

ビット判定ではシフトとANDを組み合わせる

シフト演算子は、単独で使うだけでなく、ビット演算子と組み合わせることで本領を発揮します。
特によく使うのが、指定したビットが 0 か 1 かを判定する という使い方です。

考え方はとてもシンプルです。

  1. 調べたいビットを右シフトで右端に持ってくる
  2. AND 1 を使って、その右端の1ビットだけを取り出す

指定したビットを調べる考え方

たとえば、32ビットの値 n の pos ビットを調べたいとします。

bit = n >> pos;
result = bit & 1;

この2段階で判定できます。

どういう意味なのか

まず n >> pos で、調べたい位置のビットを右端へ移動します。
すると、知りたいビットがいちばん下の位置に来ます。

そのあと bit & 1 を行うと、右端以外のビットは全部 0 になり、右端のビットだけが残ります。
その結果が 0 ならそのビットは 0、1 ならそのビットは 1 です。

具体例で見てみる

たとえば、n = 0xAA を考えます。
2進数で書くと、下位側はこうです。

10101010

ここで、ビット位置 3 を調べたいとします。

まず 3ビット右にずらします。

10101010 >> 3 = 00010101

次に 1 と AND を取ります。

00010101 & 00000001 = 00000001

結果は 1 なので、ビット 3 は 1 だとわかります。

この図は、シフト演算子と AND を組み合わせて、特定のビットを取り出す手順を示す図です。
右シフトで目的のビットを右端まで運び、そのあと AND 1 で1ビットだけを抜き出す、という流れが視覚的にわかるので、ビット判定の考え方がかなり整理しやすくなります。

ビット判定用のシンプルな関数例

ここでは、元の入力つきプログラムではなく、値と位置を固定して、関数でビット判定を行うシンプルな例に置き換えてみます。

ファイル名:16_7_2.c

#include <stdio.h>
#include <stdint.h>

int bit_check(uint32_t n, int pos);

int main(void)
{
    uint32_t num = 0xAA;
    int pos = 3;
    int result;

    /* 指定したビットの状態を確認する */
    result = bit_check(num, pos);

    printf("値 0x%X のビット%dを確認します。\n", num, pos);
    printf("ビット%dは %d です。\n", pos, result);

    return 0;
}

int bit_check(uint32_t n, int pos)
{
    /* 指定したビットを右端に移動して判定する */
    uint32_t bit = n >> pos;
    return bit & 1;
}

このビット判定プログラムでわかること

この例では、0xAA のビット3を調べています。
関数の中では、

  • n >> pos で目的のビットを右端へ動かす
  • & 1 で右端の1ビットだけを残す

という流れをそのままコードにしています。

ビット演算は難しく見えやすいですが、このように手順を分けるととてもわかりやすくなります。

シフト演算子の役割を整理する

ここまで学んだ内容を表で整理すると、シフト演算子の役割が見やすくなります。

演算子基本の役割よくある使い方
<<ビットを左へ移動する2ⁿ倍、ビット位置の移動、フラグ生成
>>ビットを右へ移動する2ⁿで割る、特定ビットの取り出し

そして、シフト演算子とビット演算子を組み合わせると、次のような操作ができます。

やりたいこと書き方の例
n ビット左にずらすvalue << n
n ビット右にずらすvalue >> n
指定ビットを調べる(value >> pos) & 1
特定ビット位置に 1 を作る1U << pos

このあたりが使えるようになると、ビット操作の幅がぐっと広がります。

シフト演算を学ぶときに意識したいこと

シフト演算は、見た目は単純でも、ビット列と数値の関係を理解するうえでとても大切な内容です。
特に意識したいのは次の点です。

意識したいこと内容
左シフトあふれがなければ 2ⁿ倍になる
右シフト符号なし整数なら 2ⁿで割るのに近い
左右の空きビット左シフトでは右が 0 埋め、右シフトは型によって異なる
符号付き整数の右シフト処理系依存なので注意する
安全な使い方符号なし固定幅整数型を使うと理解しやすい

シフト演算子は、単なる特殊な計算記号ではなく、ビット列を動かすためのとても基本的な道具です。
この感覚が身につくと、ビット判定、フラグ操作、マスク生成など、多くの低レベル処理が自然に読めるようになっていきます。