概要
C言語の入門書で一番最初に書くのはhello, worldだと思います。
#include <stdio.h>
void main(){
printf("hello, world!");
}
これを実行するとターミナルにhello, world!と表示されるわけですが、改めて考えると以下のような疑問が出てきます。
- 標準入出力とは何なのか?
- OSが管理しているのか?
- どこからどこまでがOSの管理なのか?
- ターミナルの文字のフォント、大きさなどを決めているのは誰なのか?
- CPUにどういう命令がいっているのか?
これらの疑問は結構奥深いと感じています。私も今まで、一つ解決したらまた別の疑問が生じたり、多面的な見方ができることに気付いたりといろいろ寄り道などをしながら知識を積み重ねてきました。
なかでも、生成したexeには何が入っているのか、がまず重要な観点なのではないかと思います。今回はこの観点に絞って調査したことを述べます。
今後、以下の観点でも調査記事を書きたいと思っています。
- アーキテクチャの観点で調査。
- グラフィックスパイプラインを理解する目的。
- CPUにどういう命令を渡しているのか。
- 標準入出力とは何なのか、の観点で調査。
- CUIアプリでどこまで高機能なことができるかを知る目的。
- どういう過程で画面上に文字を表示するか。
また、こういった調査の際に何を大事にしているか、などについても書く予定です。
ライブラリをリンクしている
hello worldプログラムは、実はライブラリを介して動作しています。
cで記述したプログラムをビルドするとexeができます。プログラムの実行時にはそのexeを立ち上げます。
このhello_world.exeの中身はどういうものかというと、機械語の実行バイナリです。1
exeを立ち上げると、最初に実行される命令がありますが、これをエントリーポイントと呼びます。c言語の場合はmainやwinmainという関数名をつけたものがこれになります。これは言語が決定します。
main関数が始まると、まずprintf関数を呼び出します。この実体はどこにあるかというと、ucrt.libなどの別のバイナリになります。2これはコンパイラ/リンカーが押さえている実行バイナリです。
どのlibファイルをリンクするか、はリンカーが判断します。Visual Studioだとlinkコマンドで/VERBOSEオプションをつけることでどのlibファイルがリンクされたかを出力することができます。
補足:言語が決めるか、コンパイラ/リンカーが決めるか
exeファイルに「main関数をエントリーポイントにする」という情報を埋め込んでいるのはリンカーです。リンカーは「mainという名前の関数を探してそれを先頭にする」という処理を(多分)行っています。
こう見ると、main関数をエントリーポイントにする、と決めているのはリンカーであるように思えます。しかし先ほどは言語が決めている、と表現しました。この理由を説明します。
C言語自体はプログラムではなく、規格を定めています。コンパイラやリンカーの開発者は、その規格に適合するように作ります。
エントリーポイントに関して言えば、規格に「mainをエントリーポイントにする」と明記されているので、コンパイラやリンカーの開発者が決めたのではなくC言語が決めたことになります。
また、C言語の規格の中には「ここはコンパイラの開発者が判断し、仕様を公開すること」というものもあります。(例:整数型のサイズ)この例をかんがえると腑に落ちると思います。
調査方法
今回述べたような、「実行バイナリにどういう情報が入っているか」をまず押さえることはいろいろな場面で非常に重要だと思います。
ここでは、実行バイナリを調査する際に使えるツールを紹介します。
dumpbin
Visual Studioに付属しているツールです。
exe, dllやobj、つまりPEやCOFF形式の実行バイナリを解析できます。
本記事執筆中にも、hello_world.objを解析してみたりしていました。
Visual Studio逆アセンブラ、コンパイル時アセンブラ生成
dumpbinで逆アセンブルすることもできますが、clのオプションでCソースに対応したアセンブリソースを出力することもできます。(/Faオプション)
こうすると、逆アセンブルをdumpbinで表示するよりもCソースの何行目に対応するのかがわかりやすいです。
デバッグ実行時に逆アセンブルをすることもできます。Visual Studioだと、ブレークポイントなどで止まっているときに[デバッグ]-[ウィンドウ]-[メモリ]を選択するとその時点での仮想記憶の状態が出てきます。
Visual StudiioのF12キー
Visual Studio上でC/C++ソースを書き、コンパイラ組み込みの関数など(たとえばprintf)にカーソルを合わせてF12キーを押すと、printfのプロトタイプ宣言をしているヘッダーの箇所に飛ぶことができます。
場所を調べるとWindows Kits配下だったりします。コマンドラインのツールとかでわかるものなのかは不明です。
わかっていないこと
今回の記事までに調査が間に合わなかったことを、メモ程度になってしまいますが列挙しておきます。
- objファイルは呼び出し先のシンボルを持っているだけで、どのlibで解決するかはリンカーが決める、でいいのか?
- また、#include などと書けばincludeができるが、windows.hがどこにあるのか、どういうファイルなのか、を決めているのはwindowsなのか?Visual Studioなのか?
- gccに使い慣れていけばわかってきそう
- 実行バイナリの調査がどういう観点で重要なのか
- 正解がないような話。自分の意見を出す
- lib、dllまわり。ファイル形式は何なのか、など。
最後までお読みいただきありがとうございました。
