C: ビット演算

ビット演算は照明スイッチの列の操作のようなものです——各照明が1ビットに対応し、他に影響を与えずに個別にオン、オフ、反転できます。

1. 2進数の復習

コンピュータは内部ですべてのデータを2進数で表現します。1バイト=8ビット、各ビットは0または1です。

TEXT 📖 参照専用
10進数 42  =  2進数 00101010
10進数 255 =  2進数 11111111

符号付き整数は2の補数表現を使います:最上位ビットが符号ビットで、0が正、1が負です。

C
10進数  5  =  00000101
10進数 -5  =  11111011  (2の補数)

2. 6つのビット演算子

演算子 名称 規則
& ビットAND 両方のビットが1の場合のみ結果が1
| ビットOR 少なくとも1つのビットが1なら結果が1
^ ビットXOR 2つのビットが異なる場合に結果が1
~ ビットNOT 0が1に、1が0になる
<< 左シフト ビットを左にずらし、低位は0で埋める
>> 右シフト ビットを右にずらし、高位は符号ビット(符号付き)または0(符号なし)で埋める

(1) ビットAND(&)

TEXT 📖 参照専用
  1010  (10)
& 1100  (12)
------
  1000  (8)

用途:特定のビットのクリア、特定のビットが1かどうかの確認。

(2) ビットOR(|)

C
  1010  (10)
| 1100  (12)
------
  1110  (14)

用途:特定のビットを1にセット。

(3) ビットXOR(^)

TEXT 📖 参照専用
  1010  (10)
^ 1100  (12)
------
  0110  (6)

性質:a ^ a = 0a ^ 0 = a。XORは可逆であり、同じ値で2回XORすると元に戻ります。

(4) ビットNOT(~)

C
~ 00101010  (42)
= 11010101  (-43, 2の補数)

符号なしの場合:~0 = 255(8ビット)、~0 = 0xFFFFFFFF(32ビット)。

(5) 左シフト(<<)

TEXT 📖 参照専用
5 << 2
= 00000101 << 2
= 00010100
= 20

nビット左シフトは2^n乗算と等価です。5 << 2 = 5 * 4 = 20

(6) 右シフト(>>)

C
20 >> 2
= 00010100 >> 2
= 00000101
= 5

nビット右シフトは2^n除算(ゼロ方向への丸め)と等価です。

⚠️ 注意: 符号付き負数の右シフトは高位を1で埋める(算術シフト)ため、予期しない結果になる場合があります。符号なしの値のみシフトすることを推奨します。


3. 4つの基本ビット操作

(1) ビットをセット(1にする)

TEXT 📖 参照専用
flags |= (1 << n);

n番目のビットを1にし、他のすべてのビットはそのままにします。

(2) ビットをクリア(0にする)

C
flags &= ~(1 << n);

~(1 << n)はn番目の位置だけが0で残りがすべて1のマスクを生成します。ANDを取ることでそのビットだけをクリアします。

(3) ビットをトグル(反転)

TEXT 📖 参照専用
flags ^= (1 << n);

XOR演算:0^1=1、1^1=0で、トグルが完璧に実装されます。

(4) ビットをチェック

C
if (flags & (1 << n)) {
}

n番目のビットが1なら結果は非ゼロ、0なら結果はゼロです。

(5) サンプル

C
#include <stdio.h>

void print_bits(unsigned char val) {
    for (int i = 7; i >= 0; i--) {
        printf("%d", (val >> i) & 1);
    }
    printf("\n");
}

int main(void) {
    unsigned char flags = 0;

    flags |= (1 << 3);
    printf("ビット3をセット: ");
    print_bits(flags);

    flags |= (1 << 5);
    printf("ビット5をセット: ");
    print_bits(flags);

    flags &= ~(1 << 3);
    printf("ビット3をクリア: ");
    print_bits(flags);

    flags ^= (1 << 5);
    printf("ビット5をトグル: ");
    print_bits(flags);

    flags ^= (1 << 7);
    printf("ビット7をトグル: ");
    print_bits(flags);

    if (flags & (1 << 7)) {
        printf("ビット7は1です\n");
    }

    return 0;
}
▶ 試してみよう
TEXT 📖 参照専用
ビット3をセット: 00001000
ビット5をセット: 00101000
ビット3をクリア: 00100000
ビット5をトグル: 00000000
ビット7をトグル: 10000000
ビット7は1です

4. マスク技法

マスクとは、データ内の特定のビットフィールドを抽出または変更するために使う、あらかじめ定義されたビットの組み合わせです。

(1) 下位ビットの抽出

C
unsigned int val = 0xABCD;
unsigned int low_byte = val & 0xFF;

0xFFは最下位8ビットだけを保持するマスクです。

(2) 上位ビットの抽出

TEXT 📖 参照専用
unsigned int high_byte = (val >> 8) & 0xFF;

まず8ビット右シフトし、マスクで下位8ビットを取得します。

(3) 値の結合

C
unsigned int combined = (high << 8) | low;

2バイトを16ビット値に結合します。

(4) サンプル

RGB色値の抽出。24ビット色値は赤、緑、青にそれぞれ8ビットずつ割り当てられています:

C
#include <stdio.h>

int main(void) {
    unsigned int color = 0xFF6633;

    unsigned char r = (color >> 16) & 0xFF;
    unsigned char g = (color >> 8) & 0xFF;
    unsigned char b = color & 0xFF;

    printf("色 #FF6633:\n");
    printf("  赤: %d\n", r);
    printf("  緑: %d\n", g);
    printf("  青: %d\n", b);

    unsigned int new_color = 0x00;
    new_color |= ((r / 2) << 16);
    new_color |= ((g / 2) << 8);
    new_color |= (b / 2);
    printf("暗くした色: #%06X\n", new_color);

    return 0;
}
▶ 試してみよう
TEXT 📖 参照専用
色 #FF6633:
  赤: 255
  緑: 102
  青: 51
暗くした色: #7F3319
💡 ヒント: RGB色の抽出はビット演算の古典的な応用です。Web開発の#RRGGBB形式は本質的に24ビット整数であり、赤がビット16〜23、緑がビット8〜15、青がビット0〜7です。


5. パーミッションフラグ

Linuxのファイルパーミッションはビット演算の古典的な応用です。9つのパーミッションビットが所有者/グループ/その他の読み取り/書き込み/実行を表します:

C
rwxr-xr-x = 111101101 = 0755
rw-r--r-- = 110100100 = 0644
C
#include <stdio.h>

#define READ    (1 << 2)
#define WRITE   (1 << 1)
#define EXECUTE (1 << 0)

void show_permission(unsigned char perm) {
    printf("%c", (perm & READ) ? 'r' : '-');
    printf("%c", (perm & WRITE) ? 'w' : '-');
    printf("%c", (perm & EXECUTE) ? 'x' : '-');
}

int main(void) {
    unsigned char owner  = READ | WRITE | EXECUTE;
    unsigned char group  = READ | EXECUTE;
    unsigned char other  = READ | EXECUTE;

    printf("パーミッション: ");
    show_permission(owner);
    show_permission(group);
    show_permission(other);
    printf("\n");

    owner &= ~WRITE;
    printf("書き込みを削除後: ");
    show_permission(owner);
    show_permission(group);
    show_permission(other);
    printf("\n");

    return 0;
}
TEXT 📖 参照専用
パーミッション: rwxr-xr-x
書き込みを削除後: r-xr-xr-x

6. 実用的なビットテクニック

(1) 一時変数なしで2つの変数を交換

C
a ^= b;
b ^= a;
a ^= b;

原理:XORの自己逆性。ただし、可読性が下がるため、実際の開発では推奨されません。

(2) 奇数・偶数の判定

TEXT 📖 参照専用
if (n & 1) {
}

最下位ビットが1なら奇数です。n % 2より高速です。

(3) 2の累乗での乗算・除算

C
n << 1
n << 2
n >> 1

コンパイラは通常n * 2をシフトに最適化しますが、シフトは正の整数に対してのみ安全です。

(4) 2の累乗の計算

TEXT 📖 参照専用
unsigned int pow2 = 1u << n;

1u << 0 = 11u << 1 = 21u << 8 = 256……シフトはpow(2, n)よりはるかに高速です。

(5) 2の累乗へのアライメント

C
unsigned int aligned = (value + mask) & ~mask;

例えば、4バイト境界にアライメント:(n + 3) & ~3

C
#include <stdio.h>

int main(void) {
    int values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    for (int i = 0; i < 10; i++) {
        int aligned = (values[i] + 3) & ~3;
        printf("%d -> %d\n", values[i], aligned);
    }
    return 0;
}
TEXT 📖 参照専用
0 -> 0
1 -> 4
2 -> 4
3 -> 4
4 -> 4
5 -> 8
6 -> 8
7 -> 8
8 -> 8
9 -> 12

▶ サンプル

C
#include <stdio.h>

int main(void) {
    printf("Hello, C!\n");
    return 0;
}
▶ 試してみよう

▶ サンプル —ビット演算で2のべき乗判定

C
#include <stdio.h>
#include <stdbool.h>

bool is_power_of_two(int n) {
    return n > 0 && (n & (n - 1)) == 0;
}

int main(void) {
    int vals[] = {0, 1, 2, 3, 4, 8, 15, 16, 31, 32};
    for (int i = 0; i < 10; i++)
        printf("%2d: %s\n", vals[i], is_power_of_two(vals[i]) ? "Yes" : "No");
    return 0;
}
▶ 試してみよう

n & (n-1) == 0 は2のべき乗判定の有名なテクニックです。1ビットだけ立っているかを一発で確認できます。

❓ よくある質問

Q なぜシフト演算は乗算や除算より高速なのですか?
A シフトは1クロックサイクルのCPU命令ですが、乗算や除算は複数のクロックサイクルが必要です。ただし、現代のコンパイラは2の累乗による乗算を自動的にシフトに最適化するため、手動で書く必要はありません。
Q ビット演算は浮動小数点数にも使えますか?
A 使えません。ビット演算子は整数型にのみ動作します。浮動小数点数のビットを操作するには、memcpyでバイトを整数にコピーする必要があります。
Q XOR交換の何が問題ですか?
A aとbが同じメモリ(同じ変数など)を指している場合、XOR交換は値をゼロにしてしまいます。さらに可読性が低く、コンパイラは通常の交換を同等に最適化します。

📖 まとめ

📝 練習問題

  1. 整数の2進表現における1ビットの数を返す関数を作成してください(ループで2で割るのではなく、ビット演算を使って)
  2. ビット演算を使ってRGB色の明るさを調整するプログラムを作成してください(各チャンネルに係数を掛けて再結合)
  3. 32ビット整数のビットmからビットnを抽出する関数を作成してください(m < n、ビット0から数える)
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%