C言語入門|14章の練習問題

ファイル操作とバイナリの世界へようこそ

14章では、C言語でファイルを「読み書きする」という、とても実用的なスキルを学びました。
とくに、テキストデータだけでなく バイナリデータも自由に扱える という点は、C言語の大きな強みです。

この章を理解すると、ファイルコピー、画像生成、ログ出力、ゲームデータの保存など、実際のアプリ開発でも使われる技術が一挙に身につきます。

まず、ファイル操作でよく登場する関数とその役割を表でまとめてみます。

ファイル操作で使用する基本関数

関数名書式役割
fopenFILE* fopen(ファイル名, モード)ファイルを開く(読み取り/書き込み/バイナリモードなど)
fcloseint fclose(FILE*)ファイルを閉じる
fgetcint fgetc(FILE*)1バイト読み取る(EOFまで)
fputcint fputc(int, FILE*)1バイト書き込む
freadsize_t fread(ptr, size, count, FILE*)任意サイズのバイナリ読み取り
fwritesize_t fwrite(ptr, size, count, FILE*)任意サイズのバイナリ書き込み

これらの関数は、ファイルへの入出力(I/O)を行うための標準的な道具です。

ファイルを開くモード一覧

モード意味
r読み取り(テキスト)
w書き込み(テキスト)
rb読み取り(バイナリ)
wb書き込み(バイナリ)

バイナリファイルを扱うときは rbwb を使うのが重要ポイントです。

バイナリデータとは?

テキスト:  'A' → 0x41(文字コードとして解釈)
バイナリ:  0x41 → ただの 1バイトデータ

テキストは「文字列」、バイナリは「純粋なデータ」という違いがあり、
画像や音声、動画ファイルはほとんどがバイナリデータ形式です。

BMPファイル構造とは?

ここで、この後の練習問題のためにBMPのファイル構造について簡単に説明します。
BMP(Bitmap Image File)は、Windows が標準サポートする 画像ファイル形式で、

  • とてもシンプル
  • 圧縮しない形式(24bit BMPなど)は読み書きしやすい。
  • 画像編集・ゲーム・ツール開発でも応用できる。

という特徴があります。

BMPファイルは大きく 2つの部分に分かれています。

BMPヘッダ(54バイト)の構造を表で理解する

下の表は 24bit BMP(無圧縮) におけるヘッダ情報の内訳です。

位置
(オフセット)
バイト数内容
01固定値 0x42('B')
11固定値 0x4D('M')
24ファイル全体のサイズ(リトルエンディアン)
62予約領域(通常0)
82予約領域(通常0)
104画像データの開始位置(通常 54)
144情報ヘッダのサイズ(40固定)
184画像の幅(W)
224画像の高さ(H)
262プレーン数(1固定)
2821ピクセルの色数(24 = 24bit)
304圧縮形式(0 = 無圧縮)
344画像データ領域のサイズ(0でもよい)
384水平解像度
424垂直解像度
464パレット色数(0で良い)
504重要パレット色数(0で良い)

このヘッダが 54バイトちょうど ということが「24bit BMPの特徴」です。

画像データ(ピクセルデータ)の構造

ヘッダの後には 画像データ領域が続きます。

24bit BMP では 1ピクセル = 3バイト(B, G, R) の順序です。

1ピクセルの構造(24bit BMP)

たとえば「赤を表すピクセル」は以下のようになります。

青 = 0x00
緑 = 0x00
赤 = 0xFF
→ {0x00, 0x00, 0xFF}

BMPの画像データは「下から上へ」格納される

BMPは少し変わった仕様で、画像データは 下 → 上 の順に並んでいます。

例:高さ3px の画像

ただし最近の実装では "負の高さ" を使うことで上→下にもできるため、
C言語でBMPを読み書きする際は header[22](高さ) の符号に注意が必要です。

画像データサイズの計算方法

24bit BMP の画像データ容量は:

画像データサイズ = 3 × W × H バイト

例:10 × 16 ピクセルの画像

3 × 10 × 16 = 480 バイト

メモ:BMPは「1行が4バイト境界」に揃えられる

BMP では各行のデータは 4バイト単位にパディング されます。

例:横幅 3px(必要データ 9バイト)

9バイト → 4で割ると余り1 → 3バイトのパディングを追加
→ 1行 = 12バイト

CでBMPを処理するときは

  • 1行のサイズは (3 * W + 3) & ~3
  • もしくは (4 - (3W % 4)) % 4 のパディングを計算する

と覚えておけば完璧です。

BMPヘッダを C 言語で構築する例

unsigned char header[54] = {
    0x42, 0x4D,      // 'B''M'
    0,0,0,0,         // ファイルサイズ(後で埋める)
    0,0,0,0,
    54,0,0,0,        // 画像データ開始位置
    40,0,0,0,        // 情報ヘッダサイズ
    0,0,0,0,         // 幅(後で埋める)
    0,0,0,0,         // 高さ(後で埋める)
    1,0,             // プレーン数
    24,0,            // 色数(24bit)
    0,0,0,0,         // 圧縮形式
    0,0,0,0,         // 画像データサイズ
    0,0,0,0,         // 水平解像度
    0,0,0,0,         // 垂直解像度
    0,0,0,0,         // パレット数
    0,0,0,0          // 重要パレット数
};

BMPのまとめ図(イメージ)

BMP形式の理解がなぜ重要か?

BMPはとてもシンプルなので、

  • 自分で画像を作る。
  • ピクセル単位の処理の練習をする。
  • C言語でバイナリデータを扱う基本を学ぶ。

といった用途に最適です。

14章の練習問題

【練習14-1】(テキスト→バイナリ変換プログラム)

コマンドライン引数で 2 つのファイル名を受け取り、次のように動作するプログラム text2bin を作成してください。

(1) 引数の数が 3 でなければエラーを表示して終了する。
(2) 第1引数のテキストファイルを読み取りモードで開く。開けなければ終了。
(3) 第2引数のファイルを バイナリ書き込みモード で開く。失敗したら終了。
(4) テキストファイルから1文字ずつ読み取り、その文字コード(整数値)を1バイトとしてバイナリファイルに書き込む。
(5) ファイルを最後まで処理したら両方閉じる。

【解答例】

プロジェクト名:text2bin ソースファイル名: text2bin.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    if (argc != 3) {
        printf("使用方法: text2bin <入力テキスト> <出力バイナリ>\n");
        return 1;
    }

    FILE* in = fopen(argv[1], "r");
    if (!in) {
        printf("入力ファイルを開けません。\n");
        return 1;
    }

    FILE* out = fopen(argv[2], "wb");
    if (!out) {
        fclose(in);
        printf("出力ファイルを開けません。\n");
        return 1;
    }

    int ch;
    while ((ch = fgetc(in)) != EOF) {
        unsigned char b = (unsigned char)ch;
        fwrite(&b, 1, 1, out);
    }

    fclose(in);
    fclose(out);
    return 0;
}

text.txt

以下の内容の text.txt ファイルを作成し、以下のパスに保存しておきます。ユーザー名はWindowsに現在ログインしているユーザー名です。
C:\Users\ユーザー名\source\repos\text2bin\text2bin

ABCDEFGHIJKLMNOPQRSTUVWXYZX

【解説】

  • fgetc は int を返すため EOF 判定ができる。
  • バイナリ保存用の値は unsigned char に変換
  • fwrite を使って1バイトずつ書き込む。

これはファイルコピーの応用であり、14章の理解があれば簡単に作れます。

Visual Studioでの実行方法

Visual Studio では、実行時にコマンドライン引数を設定する 必要があります。

  1. メニューから
    プロジェクト → プロパティ を選択する
  2. 左側のツリーで
    構成プロパティ → デバッグ を選択する
  3. 「コマンド引数」という項目を探す
  4. そこに次のように入力する
    text.txt text.dat
  5. 設定を保存し、
    デバッグ → デバッグの開始 を選択して実行する

【練習14-2】(赤色の 4×4 BMP を生成するプログラム)

BMP 構造を利用し、次の仕様の画像 red32x32.bmp を作成してください。

  • 画像サイズ:32×32 ピクセル
  • 色:すべて「赤」
  • 24ビット BMP(青→緑→赤 の順で1ピクセル3バイト)
  • 各ピクセルは 0,0,255 を書き込む

【解答例】(redbmp.c)

プロジェクト名:redbmp ソースファイル名: redbmp.c

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE* fp = fopen("red32x32.bmp", "wb");
    if (!fp) {
        printf("ファイルを作成できません。\n");
        return 1;
    }

    int W = 32, H = 32;
    int fileSize = 54 + 3 * W * H;

    unsigned char header[54] = {
        0x42, 0x4D,                         // 'B''M'
        0,0,0,0,                            // ファイルサイズ(後で埋める)
        0,0,0,0,
        54,0,0,0,                           // 画像データの開始位置
        40,0,0,0,                           // 情報ヘッダサイズ
        0,0,0,0,                            // 幅
        0,0,0,0,                            // 高さ
        1,0,
        24,0,                               // 24bit
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0
    };

    // ファイルサイズを埋める
    header[2] =  fileSize        & 0xFF;
    header[3] = (fileSize >> 8)  & 0xFF;
    header[4] = (fileSize >> 16) & 0xFF;
    header[5] = (fileSize >> 24) & 0xFF;

    // 幅・高さを埋める
    header[18] =  W       & 0xFF;
    header[19] = (W >> 8) & 0xFF;
    header[22] =  H       & 0xFF;
    header[23] = (H >> 8) & 0xFF;

    fwrite(header, 1, 54, fp);

    unsigned char pixel[3] = {0, 0, 255}; // 青,緑,赤 → 赤

    for (int i = 0; i < W * H; i++) {
        fwrite(pixel, 1, 3, fp);
    }

    fclose(fp);
    return 0;
}

【解説】

  • BMP 形式は「ヘッダ 54バイト」+「画像データ」
  • 1ピクセル = 青→緑→赤 の 3 バイト
  • 全て赤色なので書き込むのは {0,0,255} の繰り返し
  • 幅や高さはリトルエンディアン形式で格納

バイナリファイルの構造を直接扱う、とてもよい練習になります!

【実行結果】

画像 red32x32.bmp は以下のパスに保存されています。ユーザー名はWindowsに現在ログインしているユーザー名です。

C:\Users\ユーザー名\source\repos\redbmp\redbmp