C言語基礎|ビット単位の論理演算

ビットは小さなスイッチ!AND/OR/XOR/NOTで、ON・OFFを思い通りに組み立てよう。

ビット単位の論理演算ってなに?

第3章で学んだ「論理積(AND)」「論理和(OR)」は、条件式の true/false を扱う世界でしたよね。
一方で今回のテーマは、整数の中に詰まっている 0 と 1(ビット)そのものを対象にして、同じような論理演算をします。

イメージとしては、

  • 整数 = ビットが並んだスイッチボード
  • 0 = OFF、1 = ON
  • AND/OR/XOR/NOT で、スイッチをまとめて加工する

という感じです。ハードウェア制御、フラグ管理、ビットマスク、暗号・圧縮、通信プロトコルなど、低レイヤでは頻出の道具になります。

ビット単位の論理演算子(4種類)

ビット単位の論理演算子

演算書式意味(ビットごと)よくある用途
ANDa & b両方1のビットだけ1必要なビットだけ残す(マスク)
ORa | bどちらか1なら1フラグを立てる(セット)
XORa ^ b片方だけ1なら1差分を見る、ビット反転のトリック
NOT(補数)~a0↔1 を全部反転反転、マスクの反転

この表の説明

  • 4つとも「整数の各ビット」を独立に見て処理します
  • 浮動小数点(float や double)には使えません。整数型だけです
  • ~ は「補数演算子」と呼ばれることもあります(ビットを全部反転するからです)

0 を偽、1 を真として計算する(ビット版の真理値表)

ビット演算の結果(1ビットだけに注目)

aba & ba | ba ^ b
00000
01011
10011
11110

補数(NOT)

a~a
01
10

この表の説明

  • AND は「両方ONならON」
  • OR は「どっちかONならON」
  • XOR は「違っていればON」
  • NOT は「全部ひっくり返す」
    これを、整数の 全ビット に対して同時にやっているのがビット演算です。

図:4ビットでやると一気にイメージできる

4ビット例で図解します(下位4ビットの世界)。

この図の説明

  • 左から順に同じ桁(同じビット位置)を見て計算しています
  • 実際の unsigned が 16ビットなら16桁、32ビットなら32桁で同じことをやっています

超重要:ビット演算は「フラグ操作」で真価を発揮する

ビット演算が便利な理由は、1つの整数に複数の状態(フラグ)を詰められるからです。

ビットをフラグにする例(8ビット)

ビット位置意味(例)マスク値
0ログ出力ON1
1デバッグON2
2通知ON4
3セーフモードON8

この表の説明

  • たとえば、flags の 1ビット目が1なら「デバッグON」みたいに決めます。
  • マスク値は 1, 2, 4, 8… と 2の累乗になり、特定ビットだけを狙い撃ちできます。

よく使う操作は次の4つです。

フラグ操作の定番4パターン

やりたいこと例(mask を使う)意味
ビットを立てる(ON)flags = flags | maskORでそのビットを1にする。
ビットを消す(OFF)flags = flags & ~mask反転マスクでそのビットだけ0にする。
ビットが立ってるか調べる(flags & mask) != 0ANDして残ればON
ビットを反転するflags = flags ^ maskXORでそのビットだけ反転

登場する命令(演算子・関数)の書式と役割

本文に出てきたものを、学習用に整理します。

ビット演算子

演算子書式何をする?
&a & bビットごとのAND
|a | bビットごとのOR
^a ^ bビットごとのXOR
~~a全ビット反転(1の補数)

右シフト

本文のプログラムでは、ビット表示やビット数カウントに右シフトを使っています。

演算子書式何をする?よくある用途
>>x >> nx を右に n ビットずらす特定ビットの取り出し
>>=x >>= nx = x >> n と同じループで順にずらす

この表の説明

  • 右にずらすと、下位ビットを順番に見ていけます
  • 例えば (x >> i) & 1U で「i番目のビット」が取り出せます

サンプルプログラム

題材:フラグ管理(ON/OFF/反転/確認)
入力は1つだけ(初期フラグ値)にして、操作結果をビットで表示します。

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

// フラグ(ビット)を操作して、結果をビット列で表示する例
#include <stdio.h>

int int_bits(void)
{
    // unsigned のビット数を数える(~0U は全ビット1)
    unsigned x = ~0U;
    int bits = 0;
    while (x) {
        bits++;
        x >>= 1;
    }
    return bits;
}

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

int main(void)
{
    unsigned flags;

    printf("フラグ値(非負整数)を入力してください。\n");
    printf("flags = ");
    scanf("%u", &flags);

    // 例として3つのフラグを使う
    const unsigned LOG_ON   = 1U;  // bit0
    const unsigned DEBUG_ON = 2U;  // bit1
    const unsigned SAFE_ON  = 8U;  // bit3

    putchar('\n');
    printf("現在の flags      = "); print_bits(flags); putchar('\n');

    // 1) デバッグをON(ビットを立てる)
    unsigned after_set = flags | DEBUG_ON;
    printf("デバッグON後      = "); print_bits(after_set); putchar('\n');

    // 2) セーフモードをOFF(ビットを消す)
    unsigned after_clear = after_set & ~SAFE_ON;
    printf("セーフOFF後       = "); print_bits(after_clear); putchar('\n');

    // 3) ログを反転(ビットをひっくり返す)
    unsigned after_toggle = after_clear ^ LOG_ON;
    printf("ログ反転後         = "); print_bits(after_toggle); putchar('\n');

    // 4) デバッグがONか確認(ビットを調べる)
    printf("\n判定:デバッグは%sです。\n",
           (after_toggle & DEBUG_ON) ? "有効" : "無効");

    return 0;
}

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

  • flags | DEBUG_ON で bit1 を必ず1にする(セット)
  • flags & ~SAFE_ON で bit3 を必ず0にする(クリア)
  • flags ^ LOG_ON で bit0 を反転する(トグル)
  • after_toggle & DEBUG_ON で bit1 が立っているか調べる(テスト)

まとめ:ビット演算は「マスク」で覚えると強い

  • & は「残す(マスク)」
  • | は「立てる(セット)」
  • ~ は「反転マスクを作る」
  • ^ は「反転(トグル)」
  • は「ビットを取り出すのに便利」