ビット演算
ビット演算は照明スイッチの列の操作のようなものです——各照明が1ビットに対応し、他に影響を与えずに個別にオン、オフ、反転できます。
2進数の復習
コンピュータは内部ですべてのデータを2進数で表現します。1バイト=8ビット、各ビットは0または1です。
10進数 42 = 2進数 00101010
10進数 255 = 2進数 11111111
符号付き整数は2の補数表現を使います:最上位ビットが符号ビットで、0が正、1が負です。
10進数 5 = 00000101
10進数 -5 = 11111011 (2の補数)
6つのビット演算子
| 演算子 | 名称 | 規則 |
|---|---|---|
& |
ビットAND | 両方のビットが1の場合のみ結果が1 |
| |
ビットOR | 少なくとも1つのビットが1なら結果が1 |
^ |
ビットXOR | 2つのビットが異なる場合に結果が1 |
~ |
ビットNOT | 0が1に、1が0になる |
<< |
左シフト | ビットを左にずらし、低位は0で埋める |
>> |
右シフト | ビットを右にずらし、高位は符号ビット(符号付き)または0(符号なし)で埋める |
ビットAND(&)
1010 (10)
& 1100 (12)
------
1000 (8)
用途:特定のビットのクリア、特定のビットが1かどうかの確認。
ビットOR(|)
1010 (10)
| 1100 (12)
------
1110 (14)
用途:特定のビットを1にセット。
ビットXOR(^)
1010 (10)
^ 1100 (12)
------
0110 (6)
性質:a ^ a = 0、a ^ 0 = a。XORは可逆であり、同じ値で2回XORすると元に戻ります。
ビットNOT(~)
~ 00101010 (42)
= 11010101 (-43, 2の補数)
符号なしの場合:~0 = 255(8ビット)、~0 = 0xFFFFFFFF(32ビット)。
左シフト(<<)
5 << 2
= 00000101 << 2
= 00010100
= 20
nビット左シフトは2^n乗算と等価です。5 << 2 = 5 * 4 = 20。
右シフト(>>)
20 >> 2
= 00010100 >> 2
= 00000101
= 5
nビット右シフトは2^n除算(ゼロ方向への丸め)と等価です。
4つの基本ビット操作
ビットをセット(1にする)
flags |= (1 << n);
n番目のビットを1にし、他のすべてのビットはそのままにします。
ビットをクリア(0にする)
flags &= ~(1 << n);
~(1 << n)はn番目の位置だけが0で残りがすべて1のマスクを生成します。ANDを取ることでそのビットだけをクリアします。
ビットをトグル(反転)
flags ^= (1 << n);
XOR演算:0^1=1、1^1=0で、トグルが完璧に実装されます。
ビットをチェック
if (flags & (1 << n)) {
}
n番目のビットが1なら結果は非ゼロ、0なら結果はゼロです。
例
#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;
}
ビット3をセット: 00001000
ビット5をセット: 00101000
ビット3をクリア: 00100000
ビット5をトグル: 00000000
ビット7をトグル: 10000000
ビット7は1です
マスク技法
マスクとは、データ内の特定のビットフィールドを抽出または変更するために使う、あらかじめ定義されたビットの組み合わせです。
下位ビットの抽出
unsigned int val = 0xABCD;
unsigned int low_byte = val & 0xFF;
0xFFは最下位8ビットだけを保持するマスクです。
上位ビットの抽出
unsigned int high_byte = (val >> 8) & 0xFF;
まず8ビット右シフトし、マスクで下位8ビットを取得します。
値の結合
unsigned int combined = (high << 8) | low;
2バイトを16ビット値に結合します。
例
RGB色値の抽出。24ビット色値は赤、緑、青にそれぞれ8ビットずつ割り当てられています:
#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;
}
色 #FF6633:
赤: 255
緑: 102
青: 51
暗くした色: #7F3319
パーミッションフラグ
Linuxのファイルパーミッションはビット演算の古典的な応用です。9つのパーミッションビットが所有者/グループ/その他の読み取り/書き込み/実行を表します:
rwxr-xr-x = 111101101 = 0755
rw-r--r-- = 110100100 = 0644
#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;
}
パーミッション: rwxr-xr-x
書き込みを削除後: r-xr-xr-x
実用的なビットテクニック
一時変数なしで2つの変数を交換
a ^= b;
b ^= a;
a ^= b;
原理:XORの自己逆性。ただし、可読性が下がるため、実際の開発では推奨されません。
奇数・偶数の判定
if (n & 1) {
}
最下位ビットが1なら奇数です。n % 2より高速です。
2の累乗での乗算・除算
n << 1
n << 2
n >> 1
コンパイラは通常n * 2をシフトに最適化しますが、シフトは正の整数に対してのみ安全です。
2の累乗の計算
unsigned int pow2 = 1u << n;
1u << 0 = 1、1u << 1 = 2、1u << 8 = 256……シフトはpow(2, n)よりはるかに高速です。
2の累乗へのアライメント
unsigned int aligned = (value + mask) & ~mask;
例えば、4バイト境界にアライメント:(n + 3) & ~3。
#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;
}
0 -> 0
1 -> 4
2 -> 4
3 -> 4
4 -> 4
5 -> 8
6 -> 8
7 -> 8
8 -> 8
9 -> 12
❓ よくある質問
Q:ビット演算は浮動小数点数にも使えますか?
memcpyでバイトを整数にコピーする必要があります。📖 まとめ
- 6つのビット演算子:
&、|、^、~、<<、>> - セットは
|=、クリアは&= ~、トグルは^=、チェックは& - マスクはビット演算の核となる技法で、ビットフィールドの抽出と結合に使用
- パーミッションフラグはORで組み合わせ、ANDで確認、NOTでクリア
- 左シフトは2倍、右シフトは1/2、アライメントは
(n + mask) & ~mask
📝 練習問題
- 整数の2進表現における1ビットの数を返す関数を作成してください(ループで2で割るのではなく、ビット演算を使って)
- ビット演算を使ってRGB色の明るさを調整するプログラムを作成してください(各チャンネルに係数を掛けて再結合)
- 32ビット整数のビットmからビットnを抽出する関数を作成してください(m < n、ビット0から数える)



