
C言語基礎|ファイルのダンプ
「見えないデータを“見える化”しよう―バイナリも安心して覗ける、ダンプ入門!」
テキストファイルなら、そのまま画面に表示してもだいたい読めますよね。
でもバイナリファイルはそうはいきません。中身は“文字”ではなくバイト列なので、普通に表示しようとすると文字化けしたり、端末によっては変な制御文字が混ざって困ったりします。
そこで活躍するのが ダンプ(dump)。
ファイルの内容を 16進数(hex) と 表示できる文字(ASCII など) を並べて表示して、「どんなバイトが入っているか」を安全に確認する方法です。
デバッグや解析でめちゃくちゃ頼りになります。

ダンプって何を表示しているの?
図:ダンプ出力の1行のイメージ(16バイト表示)
00000010 48 65 6C 6C 6F 2C 20 43 21 0A 00 FF 10 20 7E 41 Hello, C!... .~A
図の説明
- 左端:その行の先頭位置(オフセット。ここでは 0x10)
- 中央:バイト値を 16進数で並べたもの(48 は 'H' など)
- 右端:表示できる文字はそのまま、表示できないバイトは . に置換
この形式だと、バイナリでも安全に中身を確認できます。
テキスト表示とダンプ表示の違い
普通に表示する vs ダンプする
| 方法 | 何が見える? | バイナリに向く? | 特徴 |
|---|---|---|---|
| 文字として表示(fgetc→putchar) | 文字として解釈できる部分だけ | 向かない | 制御文字で崩れることがある |
| ダンプ表示(fread+hex+文字) | バイト列そのもの | 向く | 何が入っていても安定表示 |
表の説明
- 「読めること」より「壊れずに確認できること」がダンプの価値です。
サンプルプログラム
ここではもっとシンプルにします。
- まず demo.bin を作る(中にわざと“表示できないバイト”も入れる)
- その demo.bin をダンプする
サンプル:デモ用バイナリを作ってダンプする
プロジェクト名:chap13-11-1 ソースファイル名:chap13-11-1.c
#include <ctype.h>
#include <stdio.h>
/* 1行に表示するバイト数 */
#define BYTES_PER_LINE 16
/* --- バイナリファイルを作る(デモ用) --- */
static void make_demo_file(const char *filename)
{
FILE *fp = fopen(filename, "wb");
if (fp == NULL) {
printf("ERROR: Cannot create demo file.\n");
return;
}
/* 表示できる文字と、制御文字/バイナリっぽい値を混ぜる */
unsigned char data[] = {
'H','e','l','l','o',',',' ','C','!','\n',
0x00, 0x01, 0x02, 0x7F, 0x80, 0xFF,
'A','B','C','1','2','3','\r','\n'
};
fwrite(data, 1, sizeof(data), fp);
fclose(fp);
}
/* --- ファイルをダンプ表示する --- */
static void dump_file(const char *filename)
{
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
printf("ERROR: Cannot open input file.\n");
return;
}
unsigned long offset = 0;
unsigned char buf[BYTES_PER_LINE];
size_t n;
printf("Dump start.\n");
while ((n = fread(buf, 1, BYTES_PER_LINE, fp)) > 0) {
/* オフセット */
printf("%08lX ", offset);
/* 16進数部 */
for (size_t i = 0; i < n; i++) {
printf("%02X ", (unsigned)buf[i]);
}
/* 最後の行が16バイト未満なら空白で揃える */
for (size_t i = n; i < BYTES_PER_LINE; i++) {
printf(" ");
}
printf(" ");
/* 文字部:表示できないものは '.' */
for (size_t i = 0; i < n; i++) {
unsigned char c = buf[i];
putchar(isprint((int)c) ? c : '.');
}
putchar('\n');
offset += BYTES_PER_LINE;
}
fclose(fp);
printf("Dump end.\n");
}
int main(void)
{
const char *fname = "demo.bin";
printf("Creating demo file...\n");
make_demo_file(fname);
printf("Dumping demo file...\n");
dump_file(fname);
printf("Done.\n");
return 0;
}このプログラムで登場する項目をしっかり解説
バイナリとして開く理由(rb)
テキストとして開く r でも読める環境は多いのですが、プラットフォームによっては改行コードの扱いなどで「読み取ったバイト列」が変わる可能性があります。
なので、ダンプは基本 rb が安心です。
モード r と rb の意図
| モード | 意味 | ダンプでのおすすめ |
|---|---|---|
| r | テキストとして読む | 基本避けたい |
| rb | バイナリとして読む | これが安全 |
表の説明
- “バイト列をそのまま確認したい”ので、バイナリモードが合っています。
fread で「まとめて読む」理由
1バイトずつ fgetc で読んでもいいのですが、ダンプでは 16バイト単位でまとめて読むと作りやすいです。
fread の書式
| 項目 | 内容 |
|---|---|
| ヘッダ | #include <stdio.h> |
| 形式 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); |
| 役割 | stream から最大 nmemb 個の size バイト要素を ptr へ読み込む |
| 返却値 | 実際に読み取れた要素数 |
今回の使い方はこうです:
- size を 1(1バイト)
- nmemb を 16(16バイト)
- 返り値 n を「今回読めたバイト数」として扱う
n = fread(buf, 1, 16, fp);
16進数表示のコツ(%02X)
1バイトは 0x00〜0xFF なので、16進で2桁表示にするのが見やすいです。
%02X の意味
| 指定 | 意味 |
|---|---|
| X | 16進(大文字) |
| 2 | 幅2桁 |
| 0 | 足りない桁は0で埋める |
だから 0x0A は A ではなく 0A と表示されます。
isprint で「表示できる文字だけ出す」理由
バイナリには、改行やタブ、ESCなどの制御コードも混ざります。
それをそのまま putchar すると、端末が変な動きをする可能性があります。
そこで isprint を使い、表示できるものだけ文字として出します。
isprint の書式
| 項目 | 内容 |
|---|---|
| ヘッダ | #include <ctype.h> |
| 形式 | int isprint(int c); |
| 意味 | c が空白を含む表示可能文字なら真(0以外) |
| 返却値 | 表示可能なら0以外、不可なら0 |
今回のロジックはこうです:
putchar(isprint((int)c) ? c : '.');
ダンプの出力が「揃っている」理由(空白埋め)
最後の行は 16バイト未満になることがあります。
そのときに16進数の列の幅が崩れると、右側の文字表示がズレて読みにくいです。
だから、足りない分を空白で埋めています。
図:最後の行だけ短い場合のズレ防止
(バイトが少ない) -> 16進数部が短い -> 文字部が左に詰まる -> 見づらい
空白で埋める -> 文字部の開始位置が一定 -> 見やすい
演習問題
演習13-13:バイナリコピー(fread / fwrite)
ファイルのコピーをバイナリファイルとして行うプログラムを作成せよ。読み書きには fread 関数と fwrite 関数を利用すること。
解答例:16バイト単位でコピー
プロジェクト名:chap13-11-2 ソースファイル名:chap13-11-2.c
#include <stdio.h>
#define BUF_SIZE 1024
int main(void)
{
FILE *in, *out;
char src[FILENAME_MAX];
char dst[FILENAME_MAX];
unsigned char buf[BUF_SIZE];
size_t n;
printf("Source file : ");
scanf("%s", src);
printf("Dest file : ");
scanf("%s", dst);
/* 入力ファイルをバイナリで開く */
in = fopen(src, "rb");
if (in == NULL) {
printf("ERROR: Cannot open source file.\n");
return 1;
}
/* 出力ファイルをバイナリで開く */
out = fopen(dst, "wb");
if (out == NULL) {
printf("ERROR: Cannot open destination file.\n");
fclose(in);
return 1;
}
/* まとめて読み、まとめて書く */
while ((n = fread(buf, 1, BUF_SIZE, in)) > 0) {
fwrite(buf, 1, n, out);
}
fclose(out);
fclose(in);
printf("Copy complete.\n");
return 0;
}解説
- テキストコピー(1文字ずつ)でも動きますが、バイナリはまとめ読みが相性抜群です。
- fwrite の第3引数は「今回読み取れたバイト数 n」を渡すのがポイントです。最後の塊は BUF_SIZE 未満になるからです。
