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

0と1を直接あやつれるようになると、C言語の表現力はぐっと広がる。

これまでC言語では、整数を「数値」として加算したり減算したりしてきました。
でも、コンピュータの内部では、その整数はすべて 0 と 1 の並び、つまりビット列として表現されています。

このビット列に対して直接操作を行うのが、ビット演算です。
ビット演算を使うと、特定のビットだけを 1 にしたり、0 にしたり、反転したりできます。
一見すると少し難しそうに見えますが、考え方はとてもシンプルです。
各ビットを1つずつ見て、対応する位置どうしでルールに従って計算しているだけです。

C言語では、ビット単位の操作を行うために、&、|、^、~ といった演算子が用意されています。
これらは、単なる知識として覚えるだけでなく、フラグ管理、ビットマスク、機器制御、通信データの処理など、実際のプログラムでもとてもよく使われます。

たとえば、ある設定をオンにする、特定の状態だけを取り出す、不要なビットを消す、といった処理は、ビット演算を使うとすっきり書けます。
特に組込み開発や低レベル処理では、ビット演算の理解がとても重要になります。

ここでは、ビット単位の論理演算子の意味と使い方を、ひとつずつやさしく整理していきます。
数字を単なる値として見るのではなく、「ビットの集まり」として扱う感覚を身につけていきましょう。

ビット単位の論理演算子とは何か

ビット単位の論理演算子は、整数型の各ビットに対して演算を行うための演算子です。
普通の論理演算子である && や || は、式全体を真か偽かで判定しますが、ビット演算子は整数の中身を構成している各ビットそのものを対象にします。

C言語で使う主なビット演算子は次の4つです。

演算子演算意味
&ビット単位のANDa & b両方のビットが1のときだけ1
|ビット単位のORa | bどちらか一方でも1なら1
^ビット単位のXORa ^ bビットが異なるときに1
~ビット単位の反転~aすべてのビットを反転する

これらは、複合代入演算子としても使えます。

書き方意味
a &= ba = a & b
a |= ba = a | b
a ^= ba = a ^ b

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

ビット単位のANDとは何か

AND は、対応するビットが両方とも 1 のときだけ、結果が 1 になる演算です。
それ以外は 0 になります。

まずは1ビットどうしの関係を表で見てみましょう。

aba & b
000
010
100
111

この表を見ると、1 になるのは 1 と 1 が重なったときだけだとわかります。

AND は必要なビットだけを残すときに使う

AND は、特定のビットだけを残して、ほかを 0 にしたいときによく使います。
この操作を マスク と呼びます。

たとえば、8ビットの値が次のようだったとします。

10101010

この値の下位4ビットを 0 にしたいときは、次のマスクを AND します。

11110000

計算するとこうなります。

  10101010
& 11110000
= 10100000

下位4ビットだけが 0 になり、上位4ビットはそのまま残っています。
つまり、1 の位置は通し、0 の位置は消す のが AND の基本的な役割です。

この図は、AND 演算が「必要なビットを残し、不要なビットを消す」操作であることを視覚的に示すための図です。
真理値表でルールを確認しつつ、具体例で実際にどのビットが残るのかを見ることで、マスクの意味がぐっと理解しやすくなります。

ビット単位のORとは何か

OR は、対応するビットの少なくとも一方が 1 なら、結果が 1 になる演算です。
両方とも 0 のときだけ 0 になります。

1ビットどうしの関係を表で見てみましょう。

aba | b
000
011
101
111

OR は特定のビットを 1 にしたいときに使う

OR は、あるビットを強制的に ON にしたいときに使います。

たとえば、次の値があるとします。

10101010

この上位4ビットを 1 にしたいなら、次の値と OR を取ります。

11110000

計算するとこうなります。

  10101010
| 11110000
= 11111010

マスク側で 1 にした位置は必ず 1 になります。
一方で、マスク側が 0 の位置は元の値がそのまま残ります。

つまり、OR は 指定したビットを ON にする 操作に向いています。

ビット単位のXORとは何か

XOR は、対応するビットが異なるときだけ 1 になる演算です。
同じなら 0 になります。

1ビットどうしの関係を表で見てみましょう。

aba ^ b
000
011
101
110

XOR は特定のビットを反転したいときに使う

XOR の大きな特徴は、1 と XOR するとビットが反転し、0 と XOR するとそのままになることです。

たとえば、次の値の下位4ビットを反転したいとします。

10101010

反転したい下位4ビットを 1 にしたマスクを使います。

00001111

計算するとこうなります。

  10101010
^ 00001111
= 10100101

下位4ビットだけが反転し、上位4ビットはそのままです。
このように、XOR は 特定のビットだけを切り替える のに便利です。

この図は、OR がビットを 1 にする操作であり、XOR がビットを反転する操作であることを対比して理解するための図です。
どちらもマスクを使いますが、結果の意味はかなり違うので、並べて見ると整理しやすくなります。

ビット単位の反転とは何か

~ は、すべてのビットを反転する演算子です。
0 は 1 に、1 は 0 になります。
これは 1の補数を求める操作に相当します。

たとえば、8ビットで次の値があるとします。

00101100

これに ~ を適用すると、次のようになります。

11010011

この演算は、すべてのビットを無条件に反転するのが特徴です。

~ を使うとマスクを簡潔に書ける

たとえば、32ビットの値 a の下位4ビットを 0 にしたいとします。
直接書くと、次のようなマスクが必要です。

a & 0xFFFFFFF0

でも、~ を使うともっと意図が見えやすくなります。

a & ~0x0F

0x0F は 2進数で書くとこうです。

00000000 00000000 00000000 00001111

これを ~ で反転するとこうなります。

11111111 11111111 11111111 11110000

つまり、下位4ビットだけ 0 のマスクが作れます。
そのため、~ は マスクを作るための道具 としてもとてもよく使われます。

ビット演算の役割を整理して比べる

ここまでの内容を表でまとめると、それぞれの役割が見やすくなります。

演算子基本の意味よくある使い方
&両方が1のときだけ1必要なビットだけ残す、特定のビットを0にする
|どちらかが1なら1特定のビットを1にする
^異なるときだけ1特定のビットを反転する
~すべて反転反転マスクを作る、全ビットを反転する

この表を見ながら整理すると、用途の違いがかなりはっきりします。

ビット演算では固定幅整数型を使うと安心

ビット演算では、何ビットで演算しているかがとても大切です。
環境によって int や long のサイズが変わると、結果の見え方が変わることがあります。

そのため、ビット演算では uint32_t のような固定幅整数型を使うと安心です。
uint32_t は、どの環境でも必ず 32ビットの符号なし整数として扱われます。

たとえば、

  • 32ビットのフラグを扱いたい
  • 32ビット固定のデータ形式を処理したい
  • ビット数をはっきりさせたい

といった場面では、uint32_t がとても便利です。

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

AND、OR、XOR、NOT の結果を確認するシンプルなプログラム例です。
表示メッセージも日本語にし、コメントも日本語にしています。

ファイル名:16_6_1.c

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

int main(void)
{
    /* 8ビットのイメージで使う値を用意する */
    uint32_t value = 0xAA;      /* 10101010 */
    uint32_t and_mask = 0xF0;   /* 11110000 */
    uint32_t or_mask = 0xF0;    /* 11110000 */
    uint32_t xor_mask = 0x0F;   /* 00001111 */

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

    printf("元の値         : 0x%02X\n", value);
    printf("ANDの結果      : 0x%02X\n", value & and_mask);
    printf("ORの結果       : 0x%02X\n", value | or_mask);
    printf("XORの結果      : 0x%02X\n", value ^ xor_mask);
    printf("NOTの結果      : 0x%08X\n", ~value);

    return 0;
}

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

このプログラムでは、値を 0xAA に固定しているので、各演算子の結果を落ち着いて確認できます。

0xAA は2進数で書くと次の形です。

10101010

これに対して、

  • AND では下位4ビットを消す
  • OR では上位4ビットを 1 にする
  • XOR では下位4ビットを反転する
  • NOT では全ビットを反転する

という変化が起こります。

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

実行結果の見方

このプログラムを実行すると、たとえば次のような結果になります。

ビット演算の結果を確認してみましょう。

元の値         : 0xAA
ANDの結果      : 0xA0
ORの結果       : 0xFA
XORの結果      : 0xA5
NOTの結果      : 0xFFFFFF55

ここで NOT の結果が 8桁で表示されているのは、uint32_t が 32ビットだからです。
もともとは 0x000000AA だったものが、全ビット反転されて 0xFFFFFF55 になっています。

この表示からも、~ は「見えている8ビットだけ」ではなく、型全体のビットを反転していることがわかります。

マスクという考え方を身につけることが大切

ビット演算を学ぶうえでは、マスク という考え方がとても大事です。
マスクとは、どのビットを操作したいかを示すための値です。

たとえば、

  • 11110000 なら上位4ビットを対象にする
  • 00001111 なら下位4ビットを対象にする

というように使います。

そして演算子ごとの役割を組み合わせると、次のような操作ができます。

やりたいこと使う形
特定のビットを残すvalue & mask
特定のビットを1にするvalue | mask
特定のビットを反転するvalue ^ mask
特定のビットを0にするvalue & ~mask

この4つは特によく使う形なので、感覚として身につけておくととても便利です。

ビット演算を学ぶときに意識したいこと

ビット演算は、最初は記号が多くて少しとっつきにくく見えるかもしれません。
でも、ひとつひとつの役割はとても単純です。

意識したいのは次の点です。

意識したいこと内容
数値をビット列として見る10進数ではなく2進数で考える習慣をつける
マスクの意味を考えるどのビットを操作したいかを意識する
演算子ごとの役割を分けて覚えるAND は残す、OR は立てる、XOR は反転、NOT は全部反転
型のビット数を意識する固定幅整数型を使うと理解しやすい

ビット演算は、整数をもっと細かく制御するための道具です。
この感覚が身につくと、フラグ処理やハードウェア制御、通信データの扱いなど、C言語らしい世界がぐっと見えてきます。

必要であれば次に続けて、同じ文体のまま
シフト演算子の解説ビットマスクを使ったフラグ管理の実践例 までつなげて作れます。