このページでは、C言語での hexdump
の自作の仕方について解説していきます。
hexdump
は Mac や Linux 等に用意されているコマンドの1つで、指定されたファイルを1バイトずつ16進数で表示するものになります。
最終的に紹介するソースコードのプログラムを実行することで、下記のような出力を得ることができるようになります(スマホだとズレるかも…)。
% ./myhexdump text.txt 496E2074686520796561722031383738 In the year 1878 204920746F6F6B206D79206465677265 I took my degre 65206F6620446F63746F72206F66204D e of Doctor of M 65646963696E65206F66207468652055 edicine of the U 6E6976657273697479206F66204C6F6E niversity of Lon 646F6E2C20616E642070726F63656564 don, and proceed 656420746F204E65746C657920746F20 ed to Netley to 676F207468726F756768207468652063 go through the c 6F757273652070726573637269626564 ourse prescribed 20666F722073757267656F6E7320696E for surgeons in 207468652061726D792E0A the army..
左側に表示されているのがファイルを1バイトずつ16進数で表示した結果になります。右側に表示されているのが、その16進数を文字コードとする文字で表示した結果となります。
hexdump
コマンドでは16進数だけでなく色んな進数に対応しているのですが、今回は16進数のみに対応するようにしていきたいと思います。
まずは、上記出力結果における左側の16進数のみの表示を行うプログラムを作成し、続いて、それに加えて右側の文字の表示も行えるようプログラムの変更を行なっていく流れで hexdump
の自作を行なっていきたいと思います。
Contents
hexdump
の自作
それでは、hexdump
を自作していきたいと思います!
まずは、文字表示なしバージョン、つまりファイルの中身を1バイトずつ16進数表示するだけのhexdump
を作成していきたいと思います。
hexdump
の自作方法
単に16進数で表示するだけであれば、hexdump
は簡単に実現できます。
処理の流れ
まず、1行に表示する16進数の個数は LINE_NUM
としたいと思います。
処理としては、まずファイルをfopen
で開き、開いたファイルから LINE_NUM
バイトのデータを読み込み、そのデータを1バイトずつ横に並べる形で16進数として出力してやれば良いだけです。
そして、LINE_NUM
バイトのデータを全て16進数で出力した際には改行を行い、その後再び LINE_NUM
バイトのデータの読み込みと出力を行います。以降、同様の処理をファイルの最後まで繰り返してやれば、ファイルのデータを全て1バイトずつ16進数で表示することができることになります。
データの読み込み
補足しておくと、ファイルから特定のバイト数のみデータを読み込む際には fread
が利用できます。
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t ntimes, FILE *stream);
例えば下記のように fread
関数を実行すれば、開いたファイル fi
から 1
バイトのデータが LINE_NUM
個読み込まれて配列 bytes
の先頭から順に格納されます。
size_t read_size = fread(bytes, 1, LINE_NUM, fi);
1
バイトのデータが LINE_NUM
個読み込まれるので、LINE_NUM
バイトのデータがファイルから読み込まれることになります。
また、実際にファイルから読み込まれたデータの個数は fread
から返却値として返却されることになります。
この返却値は基本的には第3引数で指定する LINE_NUM
となるのですが、ファイルの最後まで読み込んだ際には、LINE_NUM
個のデータを読み込むことができず、LINE_NUM
よりも小さい値が返却されることになります。
そのため、fread
の返却値が LINE_NUM
の値よりも小さくなった場合は、ファイルの最後まで読み込みが完了したと判断することができます。
16進数の出力
また、16進数で出力する際には、printf
のフォーマット指定子に %X
を指定します(%x
でも良い)。
ただ、%X
だけだと16進数の桁数がバラバラになってしまい、バイト単位の切れ目が分かりにくくなってしまいます。そのため、単に %X
と指定するのではなく %02X
と指定し、16進数が1桁の場合でも2桁で出力されるようにした方が良いと思います。
さらに、読み込んだデータは符号なしのデータとして扱うようにします(fread
の第1引数に指定する配列の型を unsigned char
にしておく)
こうすれば、1バイトずつ16進数で出力するわけですから、出力される16進数がとりうる範囲は 0x00
〜 0xFF
であり、必ず2桁以内の値となります。
なので、%02X
と指定しておけば、必ず16進数は2桁で出力することができることになります。
スポンサーリンク
hexdump
のソースコード
文字表示なしの hexdump
のソースコードは下記のようになります。
LINE_NUM
を 16
として定義していますので、1行に16バイト分の16進数が表示されます(1バイトは2桁の16進数で表示されます)。
#include <stdio.h>
#define LINE_NUM 16
int main(int argc, char *argv[]) {
unsigned char bytes[LINE_NUM];
FILE *fi;
int i;
size_t read_size;
if (argc != 2) {
printf("ダンプするファイルのパスを引数で指定してください\n");
return 0;
}
fi = fopen(argv[1], "rb");
if (fi == NULL) {
printf("%sが開けません\n", argv[1]);
return -1;
}
/* ファイルから全て読み込むまでループ */
do {
/* LINE_NUMバイトのデータをファイルから読み込み */
read_size = fread(bytes, 1, LINE_NUM, fi);
/* 読み込んだデータを1バイトずつ16進数で出力 */
for (i = 0; i < read_size; i++) {
printf("%02X", bytes[i]);
}
if (read_size < LINE_NUM) {
/* LINE_NUMバイトのデータが読み込めなかった場合 */
for (i = 0; i < LINE_NUM - read_size; i++) {
/* 足りない分だけスペースを出力してレイアウトを整える */
printf(" ");
}
}
printf("\n");
} while(read_size == LINE_NUM);
fclose(fi);
}
コンパイルして実行すれば、引数に指定したファイルを1バイトずつ16進表示した結果が得られます。
% ./hexdump text.txt 496E2074686520796561722031383738 204920746F6F6B206D79206465677265 65206F6620446F63746F72206F66204D 65646963696E65206F66207468652055 6E6976657273697479206F66204C6F6E 646F6E2C20616E642070726F63656564 656420746F204E65746C657920746F20 676F207468726F756768207468652063 6F757273652070726573637269626564 20666F722073757267656F6E7320696E 207468652061726D792E0A
基本的に行っていることは hexdump の自作方法 で解説した内容の通りです。
ただ、fread
関数で読み込めたバイト数が LINE_NUM
よりも少ない場合は(つまりファイルの最後までデータを読み込み終わった場合)、読み込んだバイト数が LINE_NUM
に足りない分、16進数ではなく2つのスペース " "
を出力するようにしています。
これは、各行で表示される桁数を LINE_NUM * 2
に揃えるためであり、もっと言えば次に行う文字表示を行う際にレイアウトが崩れないようにするためです。
ですので、実は16進数の表示を行うだけであれば、下記の処理はなくても問題ありません。
if (read_size < LINE_NUM) {
/* LINE_NUMバイトのデータが読み込めなかった場合 */
for (i = 0; i < LINE_NUM - read_size; i++) {
/* 足りない分だけスペースを出力してレイアウトを整える */
printf(" ");
}
}
また、16進数表示を行うファイルのパスはコマンドライン引数で受け取るようにしています。
コマンドライン引数については下記ページで解説していますので、詳細な説明を読みたい方は下記ページをご参照いただければと思います。
【C言語】コマンドライン引数の扱い方(VSCodeでの引数指定方法も解説)hexdump
の自作(文字表示あり)
続いて、hexdump の自作 で開発した hexdump
に文字表示機能を実装していきたいと思います。
hexdump
の自作方法(文字表示あり)
hexdump の自作 で開発した hexdump
では、ファイルから読み込んだデータを LINE_NUM
バイト分ずつ横に並べて16進数表示するようにしています。
次は、その LINE_NUM
バイト分の16進数の横に、LINE_NUM
バイト分の文字を表示するようにしていきます。
処理の流れ
fread
関数でのデータの読み込みにより配列 bytes
に LINE_NUM
バイト分のデータが格納されているわけですから、LINE_NUM
バイト分の16進数の表示を行なった後に、配列 bytes
のデータを先頭から順に1バイトずつ LINE_NUM
個分の文字として出力してやれば、上の図のような文字表示を実現することができます。
文字の出力
ただ、16進数表示の時とは異なり、文字としてデータを表示するわけですから、printf
のフォーマット指定子には %c
を指定する必要があります。
printf
では、同じデータであってもフォーマット指定子の指定によって異なる形式で出力を行うことができます。
また、単純に文字として printf
で出力を行なった場合、その文字が改行文字の場合は変に改行が入ってしまって表示結果のレイアウトが崩れてしまいます。タブ文字などの場合も同様に表示結果のレイアウトが崩れてしまいます。
さらに、読み込み先のファイルがテキストファイルでない場合や、テキストファイルであっても全角文字がある場合などは、読み込んだデータによっては printf
で出力を行なった際に文字化けが発生する可能性があります。
このため、今回は出力する文字が “印字可能な文字 or スペース” である場合のみprintf
で出力を行い、それ以外の場合は printf
で .
を出力するようにしたいと思います。
下記ページで解説していますが、文字が “印字可能な文字 or スペース” であるかどうかは isprint
関数により調べることができます。
スポンサーリンク
hexdump
のソースコード(文字表示あり)
文字表示ありの hexdump
のソースコードは下記のようになります。
#include <stdio.h>
#include <ctype.h>
#define LINE_NUM 16
int main(int argc, char *argv[]) {
unsigned char bytes[LINE_NUM];
FILE *fi;
int i;
size_t read_size;
if (argc != 2) {
printf("ダンプするファイルのパスを引数で指定してください\n");
return 0;
}
fi = fopen(argv[1], "rb");
if (fi == NULL) {
printf("%sが開けません\n", argv[1]);
return -1;
}
/* ファイルから全て読み込むまでループ */
do {
/* LINE_NUMバイトのデータをファイルから読み込み */
read_size = fread(bytes, 1, LINE_NUM, fi);
/* 読み込んだデータを1バイトずつ16進数で出力 */
for (i = 0; i < read_size; i++) {
printf("%02X", bytes[i]);
}
if (read_size < LINE_NUM) {
/* LINE_NUMバイトのデータが読み込めなかった場合 */
for (i = 0; i < LINE_NUM - read_size; i++) {
/* 足りない分だけスペースを出力してレイアウトを整える */
printf(" ");
}
}
printf(" ");
/* 読み込んだデータを1バイトずつ文字で出力 */
for (i = 0; i < read_size; i++) {
if (isprint(bytes[i])) {
/* 印字可能な文字の場合はそのまま出力 */
printf("%c", bytes[i]);
} else {
/* 印字可能でない場合は.を出力 */
printf(".");
}
}
printf("\n");
} while(read_size == LINE_NUM);
fclose(fi);
}
hexdump のソースコード からの変更点は ctype.h
のインクルードの追加と、下記の処理の追加のみとなります。
/* 読み込んだデータを1バイトずつ文字で出力 */
for (i = 0; i < read_size; i++) {
if (isprint(bytes[i])) {
/* 印字可能な文字の場合はそのまま出力 */
printf("%c", bytes[i]);
} else {
/* 印字可能でない場合は.を出力 */
printf(".");
}
}
で、この追加した処理の中で、hexdump の自作方法(文字表示あり) で解説した処理を行なっています。
もし、isprint
関数を利用せずに全てのデータを printf("%c", bytes[i])
で出力した場合は、改行が含まれるファイルの場合は下記のような出力になってレイアウトが崩れてしまいます。
% myhexdump text.txt 496E2074686520796561722031383738 In the year 1878 204920746F6F6B206D79206465677265 I took my degre 65206F6620446F63746F720A6F66204D e of Doctor of M 65646963696E65206F66207468652055 edicine of the U 6E6976657273697479206F66204C6F6E niversity of Lon 646F6E2C0A616E642070726F63656564 don, and proceed 656420746F204E65746C657920746F20 ed to Netley to 676F207468726F756768207468652063 go through the c 6F757273650A70726573637269626564 ourse prescribed 20666F722073757267656F6E7320696E for surgeons in 207468652061726D792E0A the army.
ですが、上記のように isprint
関数を利用して文字が “印字可能な文字 or スペース” であるかどうかを判別し、その結果に応じて処理を切り替えるようにすることで、改行が含まれるファイルの場合でも下記のようにレイアウトを崩さずに文字の表示を行うことができます。
496E2074686520796561722031383738 In the year 1878 204920746F6F6B206D79206465677265 I took my degre 65206F6620446F63746F720A6F66204D e of Doctor.of M 65646963696E65206F66207468652055 edicine of the U 6E6976657273697479206F66204C6F6E niversity of Lon 646F6E2C0A616E642070726F63656564 don,.and proceed 656420746F204E65746C657920746F20 ed to Netley to 676F207468726F756768207468652063 go through the c 6F757273650A70726573637269626564 ourse.prescribed 20666F722073757267656F6E7320696E for surgeons in 207468652061726D792E0A the army..
特にバイナリデータを文字として出力する場合、そのまま出力するとレイアウトが崩れたり文字化けしたりしてしまいます。
それを防ぐために、下記ページで紹介している isprint
関数などの文字を判別する関数が活躍しますので、これらの関数の存在は覚えておくと良いと思います。
まとめ
このページでは、C言語での hexdump
の自作の仕方について解説しました!
割と単純な処理で自作することが可能ですが、バイナリデータの扱いに慣れることもできますし、同じデータでも printf
のフォーマット指定子によって出力形式の変更が可能であることを実感できる例だと思います。
また、今回は hexdump
の自作について解説しましたが、他のコマンドを自作してみることでプログラミングの力を伸ばすこともできると思います!
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/