C言語のきほん|ビット操作の実践問題集

手を動かして試すほど、ビット操作はぐんと身につく。

ビット演算やシフト演算は、最初は少しとっつきにくく感じやすい分野です。
&、|、^、~、<<、>> といった記号がたくさん出てきて、意味を覚えるだけでも大変に見えるかもしれません。

でも、ビット操作は「読むだけ」で理解するよりも、実際に問題を解いて手を動かす ことで一気にわかりやすくなります。
たとえば、特定のビットを ON にする、OFF にする、反転する、左右にずらす、ビット列を表示する、2の補数を求めるといった処理は、1つずつ試していくとルールがかなり整理されてきます。

ビット操作のよいところは、考え方が一度つかめると応用が広いことです。
フラグ管理、通信データの解析、組込み制御、ハードウェア操作、エラー検出、データ圧縮など、さまざまな場面で同じ基本技術が使われています。
つまり、練習問題を通して身につけた力が、そのまま実践的なプログラムにつながりやすいのです。

ここでは、ビット操作に関する代表的な練習テーマを振り返りながら、考え方の整理と実践力の強化をねらっていきます。
さらに、いただいた問題例と同じ流れで、新しい類似問題も1つ作成し、解答例と解説までまとめます。
最後には、確認問題も用意して、理解がどこまで定着しているかをチェックできるようにしていきましょう。

ビット操作の実践問題で身につけたいこと

実践問題に取り組むときは、ただ答えを出すだけではなく、「どの演算子をどう使うか」を意識することが大切です。
ビット操作では、見た目のコードは短くても、内部では明確な意味があります。

まずは、代表的なテーマを整理してみましょう。

テーマ身につくこと
2の補数を求める反転と加算の関係、負数表現の理解
ビットパターンを表示するシフトとマスクの基本
特定ビットを ON / OFF にするOR、AND、反転マスクの使い方
ビット列を左右反転するシフト操作と位置の入れ替え
パリティを調べるビット単位の検査、エラー検出の基礎

こうして見ると、ほとんどの問題は シフトビット演算子 の組み合わせでできています。
つまり、個別の問題に見えても、根っこにある考え方はかなり共通しています。

実践問題でよく使う基本パターン

ビット操作の問題では、よく出てくる定番の書き方があります。
これを先に整理しておくと、問題を解くときの見通しがかなりよくなります。

やりたいこと書き方の例意味
特定ビットを調べる(n >> pos) & 1pos ビットを右端へ動かして取り出す
特定ビットを ON にするn | (1U << pos)その位置に 1 を立てる
特定ビットを OFF にするn & ~(1U << pos)その位置だけ 0 にする
特定ビットを反転するn ^ (1U << pos)その位置だけ切り替える
全ビットを反転する~n0 と 1 をすべて反転する

この表に出てくる 1U << pos という形はとても重要です。
これは「pos の位置だけが 1 のビット列を作る」という意味で、ビット操作では本当によく使います。

問題を解くときの考え方

ビット操作の問題は、いきなりコードを書こうとすると混乱しやすいです。
そんなときは、次の順番で考えるとかなり整理しやすくなります。

手順考えること
1どのビットを対象にしたいかを決める
2そのビット位置に対応するマスクを作る
3何をしたいかを決める
4適切な演算子を選ぶ

たとえば、「ビット5を ON にしたい」なら、

  1. 対象はビット5
  2. マスクは 1U << 5
  3. ON にしたい
  4. OR を使う

という流れになります。

こう考えるだけで、書くべき式はかなり自然に見えてきます。

実践問題の流れを振り返る

いただいた問題例では、段階的にビット操作の理解が深まるように構成されています。

最初は 2の補数のような基本的なビット変換から始まり、次にビットパターンの表示、ビットの ON / OFF、ビット列の反転、さらにパリティチェックのような応用問題へと進んでいます。

この流れはとてもよくできていて、

  • 基本の演算を知る
  • 実際にビット列を見る
  • 特定ビットを操作する
  • ビット列全体を扱う
  • 実用的な誤り検出につなげる

という学び方になっています。
つまり、単に演算子を覚えるのではなく、「ビット列をどう扱うか」という感覚を育てる流れになっているわけです。

実践問題

次の仕様に従って関数を作成し、main関数から呼び出して結果を確認してください。

関数宣言:uint32_t bit_toggle(uint32_t n, int pos);

機能:n の pos ビットを反転する。
返却値:指定したビットを反転した結果を返す。
補足:最下位ビットは pos = 0、最上位ビットは pos = 31。

実行結果例

0 ~ 0xffffffff の16進数を入力してください。> f0f0f0f0
反転するビット位置(最下位:0 ~ 最上位:31)> 4

元のビットパターン
1111 0000 1111 0000 1111 0000 1111 0000

ビット4を反転した結果
1111 0000 1111 0000 1111 0000 1110 0000

この問題の考え方

この問題では、指定した位置のビットだけを切り替えます。
0 なら 1 に、1 なら 0 に変えたいので、使う演算子は XOR です。

使うマスクは次の形です。

1U << pos

そして、そのマスクと XOR を取れば、その位置のビットだけを反転できます。

n ^ (1U << pos)

これがこの問題の中心になります。

解答例

ファイル名:16_9_1.c

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

uint32_t bit_toggle(uint32_t n, int pos);
void print_bits(uint32_t n);

int main(void)
{
    uint32_t num;
    int pos;
    uint32_t result;

    printf("0 ~ 0xffffffff の16進数を入力してください。> ");
    if (scanf("%x", &num) != 1) {
        printf("入力エラーです。\n");
        return 1;
    }

    printf("反転するビット位置(最下位:0 ~ 最上位:31)> ");
    if (scanf("%d", &pos) != 1 || pos < 0 || pos > 31) {
        printf("ビット位置が範囲外です。\n");
        return 1;
    }

    result = bit_toggle(num, pos);

    printf("\n元のビットパターン\n");
    print_bits(num);

    printf("\nビット%dを反転した結果\n", pos);
    print_bits(result);

    return 0;
}

uint32_t bit_toggle(uint32_t n, int pos)
{
    return n ^ (1U << pos);
}

void print_bits(uint32_t n)
{
    int i;

    for (i = 31; i >= 0; i--) {
        printf("%u", (n >> i) & 1U);

        if (i % 4 == 0 && i != 0) {
            printf(" ");
        }
    }

    printf("\n");
}

解答例のポイント

この解答では、bit_toggle 関数の中心はとてもシンプルです。

return n ^ (1U << pos);

ここで使っている 1U << pos は、pos の位置だけが 1 のマスクを作っています。
そのマスクと XOR を取ることで、その位置だけが反転します。

また、print_bits 関数では、上位ビットから順に右シフトし、& 1U で1ビットずつ取り出して表示しています。
4ビットごとに空白を入れているので、ビット列がかなり見やすくなっています。

なぜ XOR を使うのか

XOR の性質を表で見ると、理由がはっきりします。

元のビットマスクのビット結果
000
101
011
110

この表を見ると、

  • マスクが 0 の位置は変わらない
  • マスクが 1 の位置は反転する

ということがわかります。

だからこそ、特定の1ビットだけを反転したい問題では、XOR がとてもぴったり合います。

実践問題に取り組むときのコツ

ビット操作の問題では、式を丸暗記するよりも、次のような考え方を意識すると強いです。

コツ内容
ビット列を書いてみる2進数にすると操作の意味が見えやすい
まず1ビットで考えるAND、OR、XOR の意味が整理しやすい
マスクの形を意識するどこが 1 で、どこが 0 かが重要
シフトの意味を考える目的のビットを移動していると考える
固定幅整数型を使うuint32_t などを使うと理解しやすい

特に、難しく感じたら「今どのビットを見たいのか」をはっきりさせると、かなり整理しやすくなります。

16章の確認問題:ビット操作の確認

次の項目について、正しいものには○、間違っているものには×をつけてください。

① uint32_t は、処理系に依存せず常に 32ビット幅を持つ符号なし整数型である。
② 式 n & 1 は、n の最上位ビットが 0 か 1 かを判定する式である。
③ 式 n | (1U << pos) は、n の pos ビットを 1 にするために使える。
④ 式 n & ~(1U << pos) は、n の pos ビットを反転する式である。
⑤ 式 n ^ (1U << pos) は、n の pos ビットだけを反転するために使える。
⑥ 左シフトでは、右側の空いたビットは 0 で埋められる。
⑦ 符号なし整数の右シフトでは、左側の空いたビットは通常 0 で埋められる。
⑧ 符号付き整数の右シフトは、C言語では必ず算術シフトになる。
⑨ ビットパターンを上位ビットから表示したいときは、右シフトと & 1 を組み合わせて1ビットずつ取り出せる。
⑩ XOR 演算子は、指定したビットを 0 に固定するためだけに使われる。

解答と解説

① ○
uint32_t は stdint.h で定義される固定幅整数型で、常に 32ビットの符号なし整数として扱われます。移植性を高めたいビット操作ではとても便利です。

② ×
n & 1 で調べられるのは最下位ビットです。最上位ビットではありません。右端の1ビットだけを取り出す処理になります。

③ ○
1U << pos で pos ビットの位置だけが 1 のマスクを作り、それを OR すると、そのビットを 1 にできます。

④ ×
n & ~(1U << pos) は、n の pos ビットを 0 にする式です。反転ではありません。反転したいときは XOR を使います。

⑤ ○
n ^ (1U << pos) は、指定した位置だけを反転します。マスクが 1 の位置だけが切り替わるのが XOR の特徴です。

⑥ ○
左シフトでは、右側に空いたビットは 0 で埋められます。左側からあふれたビットは捨てられます。

⑦ ○
符号なし整数の右シフトは、通常、左側が 0 で埋められる論理右シフトとして考えられます。

⑧ ×
符号付き整数の右シフトは処理系依存です。多くの環境では算術シフトになりますが、必ずそうなるとは限りません。

⑨ ○
n >> i で目的のビットを右端に移動し、そのあと & 1 を行えば、その1ビットだけを取り出せます。ビット表示の基本パターンです。

⑩ ×
XOR は指定したビットを反転するために使います。0 に固定する専用の演算ではありません。0 にしたいなら AND と反転マスクを使います。

ビット操作を学ぶ意味

ビット操作は、最初は細かくて難しそうに見えます。
でも、1つずつのルールはとてもシンプルです。
そして、そのシンプルなルールを組み合わせることで、かなり柔軟で実践的な処理ができるようになります。

今回のような問題集形式で学ぶと、

  • 演算子の意味を理解する
  • ビット列の変化を確認する
  • 関数としてまとめる
  • 結果を表示して確かめる

という流れで学べるので、知識がかなり定着しやすくなります。
ビット操作は、覚えるというより、慣れていく ことが大切な分野です。
そのため、こうした実践問題を何問か解いていくと、記号の意味が自然に読めるようになっていきます。