C/C++の実行コードにバイナリを埋め込む
ニッチな記事を書くのが好きです
私が今いる Web 業界では、C/C++ 言語が使われることはほとんどなくなりました。ですが、むしろこの業界が特殊なだけで、C/C++ は様々なところで非常によく使われている……と思っています。
Zenn の読者は Web 業界の方が多いと勝手に思っているので、こんなネタが必要なのかは分からないんですが、いや、こういうのは自己満足です。書いてて楽しければいいんです! ということで書きました。そのついでで、いつか誰かの役に立つなら御の字というものです。
そういうところを踏まえたうえで「理由とか必要性とかどうでもいい、楽しそうだからやってみたい!」という同類の方はどうぞご覧ください😉
何のためにバイナリを埋め込むのか
プログラムが利用するファイルは、たいてい、必要なときに、必要なものを読み込みます。
ファイルを読み込むには、OSのシステムコールを呼び出します。C/C++ の場合、これは fopen()
や fread()
などの関数を経由して行われます。
これらの呼び出しには(程度はさておき)時間がかかります。
しかしこれらのファイルをあらかじめ埋め込んでおけば、読み込まずに済みます。すると、そのための関数・システムコールを呼び出すことがなくなります。これが速度向上につながる、というわけです。
ただし! ただしですよ、これらの関数・OSのシステムコールの呼び出しにかかるコストは、現代では、大抵の場合ほとんど無視できる速度だと思います。(実際には計測してみないとわかりませんが)
どんなときに有効か
必ず読み込まれるファイルの場合
前節でも書きましたが、ファイルは、たいてい、必要なときに、必要なものを読み込みます。
しかし中には必ず読み込まれるものもあるはずです。そういうものを埋め込んでおくと無駄になりません。
また、起動時にはそういうものが結構あると思います。その場合、プログラムの初速を上げることができるかもしれません。
その他にも注意点がいくつかあります:
- プログラムが置かれている媒体と、データ用の媒体が別になっていて、データ用の媒体の方が高速にアクセスできる場合、むしろ遅くなることがあります
- あらかじめバイナリを埋め込んでおくと、プログラム自体が大きくなるので、起動時点のメモリ使用量が増えることになります
- キャッシュがよく効く場合、ファイルを読み込むコストはさらに下がるでしょう
- 埋め込む量が多くプログラムが大きくなると、プログラム自体がキャッシュに乗らなくなる可能性があります
などなど、いろいろな要素を考慮する必要があります。何かの状況でこの方法が有効かもしれない、と考えたときはきちんと計測してみることをお勧めします。
大量に小さなファイルを読み込む場合
繰り返しますが、関数・システムコールの呼び出しコストは、現代ではほとんど無視できるでしょう。
……が、それでもファイルが小さく、大量にある場合は別です。1万とか10万とかね[1]!
読み込んだデータを格納するのに malloc()
でヒープ領域を確保することが多いでしょうが、これにもコストがかかります。こちらはデータ量ではなく、データ数に比例するコストなので、小さなファイルを大量に読み込むときは、今でもそこそこコストがかかるはずです[2]。(たぶん)
塵も積もれば山となります。こういうところは節約したいものです[3]。
原理(結論)
そんなに特別なことはしていません。埋め込みたいファイルを1バイトずつ読み込み、C/C++ の16進数表記(たとえば0x42
など)に変えて、char
配列[4]を作ってヘッダファイル(.h
)に書き込むというものです。
C/C++ のコードからこのヘッダファイルを include
すれば、fopen()
, fread()
(場合によっては malloc()
)を使うことなく、char
配列にできる、というわけです。
コード
では、実際のコードをお見せして、説明したいと思います。
コード自体は、そんなに難しいことをしているわけではありません。fgetc()
でファイルを読み込み、fprintf()
で書き込むだけです。
サクッと compile & link しましょう[5]。
機種依存しないように作っているつもりですが、もし問題があれば教えてください。
あと、このプログラム自体は頻繁に使うものではないと思うので、これ自体の速度は気にしていません。PR お待ちしています😊
gcc -O3 binary-to-array.c -o binary-to-array
使い方
echo -n "Hello, World" | ./binary-to-array hello
hoge_data.h
には以下のような内容が書き込まれます。なお、出力は見通しをよくするのと、1行あたりの文字数が多いとエディタに負荷がかかるので、10バイトごとに改行しています。
const unsigned char hello_array[] = {
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72,
0x6c, 0x64,
};
const unsigned long hello_size = 12;
サンプル代わりの、ちょっとイカした使い方
指定したディレクトリにあるファイルをすべて char[]
に変えて .h
に書き込む、ということを一括で行うスクリプトを書いてみました。しかも、その 個々の .h
ファイルをまとめた .h
ファイルも作成します。これで1万でも10万でも楽勝ッス!
余談
最初に書いたように、これはあくまで自己満足です。それでもまあ、実際、別に作っているプログラムで必要だったので、車輪の再発明だろうとは思いつつも手作りすることにしました。
やっぱり C/C++ のコードを書くのは楽しいものです。この、何かを無理矢理ゴリゴリいじっている感じがたまらんですね😊
最後に、本当に使いたい人向けの情報を:
- Debian 系の Linux ディストリビューションを使っている方は
hxtools
というパッケージにbin2c
というコマンドがあるようなのでこちらを検討してもいいのでは。(ただ私は使ったことがないので、よくわかりません) - あと、Adobe も作っていました。これもいいかもしれませんね。
https://github.com/adobe/bin2c
Discussion