ブログっ...!

自分についてのメモしかないので、見る価値がないよ。(人に説明する思いやりが皆無に近いよ。)

まなび1

トライ木の話になった。
トライ木って、人工知能の辞書とかの恐ろしく大きいデータのためのデータ構造だと思っていたのだが。

私はほぼ毎日と言っていい程、このトライ木にお世話になっていたらしい。

以下はMakefileの一例。
all : foo.exe
foo.exe : foo.o
gcc foo.o -o foo
foo.o : foo.c
gcc -c foo.c -o foo.o

MakefileのこのA : Bという記法は、Aの生成にはBが必要という意味。
make Aとやれば、Aが生成される。
$ make
は make all の略。

この各々のファイル (func.o, func.cなど) は
C言語がコンパイルされて実行可能になるまでの流れ - にょきにょきブログ
で丁寧に説明されている。

foo.c から foo.exeに至るまで、

foo.c --cpp--> foo.i[-E] --cc1--> foo.s[-S] --as--> foo.o[-c] --ld--> foo.exe [-なし]

こんな過程がある。
・cpp : Cプリプロセッサ
・cc1 : Cコンパイラ
・as : アセンブラ

(A)「 --cpp--> --cc--> --as--> 」部が gcc がやっているもの。 gccのことをコンパイラだと思っていた今までだったが、実は「コンパイラドライバ」 コマンドを実行していろいろなツールを動かす役目。 clangは (A) 部をまとめて行うため、コンパイラと呼んでよいらしい。

foo.c
int main() { puts("hello"); }

foo.sを生成すると、アセンブリ語がこんな感じで並んでいるイメージ 左はアドレスだと思って

foo.s

a ラベル 命令
0 main: push hoge
5 call _puts
10 ret
12 hoge: .string "hello\0"

foo.sからfoo.o間はアセンブラの仕事。 上記の例だと、hogeが示すアドレスは一体何かを具体化する。hoge は 12 に置換されるイメージ。 ファイル内で閉じてアドレスの解決を行うために、並列作業が可能である。(.oを作るまではマップ並列)。 たとえfoo.c, bar.c の中に依存関係があっても、ファイル内で閉じていた解決なので。 この時点ではputsのアドレスはまだ解決されない。 プロトタイプ宣言はされているが、実体は定義されていない。 (他のファイルでputsが定義されている場合はリンカのお仕事、この場合はlibcの関数なので後のローダのお仕事) これ以降はローダのお仕事。

リンカは規格の決まったヘッダーを用意する。
ELF, Mach-O (object file format) $ file file.o で見るとよい。
-------
|header|
-------
| text |
-------
| .data |
-------
| .rel |
-------
まだ解決されないアドレスを rel に載せ、relはtextを参照する形になっている。

一方、libcも同じ過程を経て、リンカに渡る。 リンカは puts は アドレスA, printf は アドレスB, ... みたいな巨大な参照関係を示したトライ木を作成し(Mach-Oの場合. ELFはハッシュ)、それをローダに引き渡す。 libc.so/ libc.dylib

ローダーは「実行時になるまでわからないメモリのどこか」にプログラムが配置された後、 これによって決定された実際の(?)アドレスを各ファイルに教えて、参照関係を再びただす。

実行時には各ファイルのアドレスが依存するために、ローダーの作業を並列化しても高速化は望めないらしい。 リンカもリデュース。