g++でhello.cppから実行ファイルを生成するときに何が起こっているかを調べてまとめました。全体的に gcc Compilation Process and Steps of C Program in Linux を参考にしています。元記事はCファイルを対象にしていますが、本記事ではC++ファイルを対象としています。
ソースコード
以下のコード hello.cpp をステップバイステップでビルドしていきます。このコードはC++11相当のコードです。
#include <iostream> #include <vector> #define MESSAGE "hello " int main(void) { std::vector<int> vec{1,2,3,4,5}; for(auto e: vec) { std::cout << MESSAGE << __FILE__ << " " << e << std::endl; } return 0; }
1. プリプロセッシング
プリプロセッシングの結果を見る
まず最初に、プリプロセッシングと呼ばれる前処理が行われます。具体的には、
#include
でインクルードしたヘッダファイルの展開#define
で定義した文字列の置換処理__FILE__
や__LINE__
などの記号定数の置換処理
が行われます。(参考:マクロ展開の例)
$ cpp -x c++ -std=c++11 hello.cpp > hello.i
または
$ g++ -E -std=c++11 hello.cpp > hello.i
で、プロプロセッシング後のファイルをhello.i
という名前で保存できます。
hello.i
の抜粋を以下に示します。
# 1 "hello.cpp" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "hello.cpp" # 1 "/usr/include/c++/5/iostream" 1 3 # 36 "/usr/include/c++/5/iostream" 3 ...(中略)... # 5 "hello.cpp" int main(void) { std::vector<int> vec{1,2,3,4,5}; for(auto e: vec) { std::cout << "hello " << "hello.cpp" << " " << e << std::endl; } return 0; }
ヘッダファイルが検索されるディレクトリの場所を調べる
ヘッダファイルが捜索されるディレクトリの場所は、Cの場合は
$ gcc -xc -E -v -
C++の場合は
$ gcc -xc++ -E -v -
で調べることができます(参考:c++ - What are the GCC default include directories? - Stack Overflow)。私の場合は以下のように表示されました。
(略) #include "..." search starts here: #include <...> search starts here: /usr/include/c++/5 /usr/include/x86_64-linux-gnu/c++/5 /usr/include/c++/5/backward /usr/lib/gcc/x86_64-linux-gnu/5/include /usr/local/include /usr/lib/gcc/x86_64-linux-gnu/5/include-fixed /usr/include/x86_64-linux-gnu /usr/include End of search list.
includeされるヘッダファイルのフルパスを調べる
ヘッダファイルは芋づる式にincludeされていくわけですが、具体的にどのパスにあるどのヘッダファイルがincludeされたかは、
$ g++ -std=c++11 -H hello.cpp
で調べることができます(参考:c++ - How to tell where a header file is included from? - Stack Overflow)。私の環境だと以下のようにツリーが表示されました。
$ g++ -H hello.cpp . /usr/include/c++/5/iostream .. /usr/include/x86_64-linux-gnu/c++/5/bits/c++config.h ... /usr/include/x86_64-linux-gnu/c++/5/bits/os_defines.h .... /usr/include/features.h ..... /usr/include/x86_64-linux-gnu/sys/cdefs.h ...... /usr/include/x86_64-linux-gnu/bits/wordsize.h ..... /usr/include/x86_64-linux-gnu/gnu/stubs.h ...... /usr/include/x86_64-linux-gnu/gnu/stubs-64.h ...(略)...
2. アセンブリコードへの変換
次に、コンパイラは、hello.i
をアセンブリコードに変換します。
$ g++ -std=c++11 -S hello.i
とすると、アセンブリコードhello.s
が生成されます。
.file "hello.cpp" .section .rodata .type _ZStL19piecewise_construct, @object .size _ZStL19piecewise_construct, 1 _ZStL19piecewise_construct: .zero 1 .local _ZStL8__ioinit .comm _ZStL8__ioinit,1,1 ...(略)...
3. オブジェクトファイルへの変換
次に、as
コマンドを使って、アセンブリコードhello.s
からマシン語に変換し、オブジェクトファイルhello.o
を得ます。
$ as hello.s -o hello.o
hello.o
の中身は
$ objdump -d hello.o
で調べることができます。以下は実行例です。
0000000000000000 <main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 41 55 push %r13 6: 41 54 push %r12 8: 53 push %rbx 9: 48 83 ec 58 sub $0x58,%rsp d: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax ...(略)...
4. リンク
実行ファイルを生成する
最後に、さっき生成したオブジェクトファイルhello.o
および、他に必要なオブジェクトファイルを集めてリンクし、実行ファイルを生成します。
$ g++ hello.o -o hello
とすると、実行ファイルhello
が生成されます。
ライブラリが検索されるディレクトリの場所、リンクされるライブラリのフルパスを調べる
$ g++ -v hello.o -o hello
とすると、以下のように詳細情報が表示されます。
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../:/lib/:/usr/lib/
は、ライブラリが検索されるディレクトリの場所を表していると思います。
また、
COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-shared-libgcc' '-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccvdOxv1.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro -o hello /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. hello.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
は、リンク時に使われたコマンドの詳細を表していると思います。collect2
とはgcc/g++が内部で呼び出すリンクのためのコマンドのようです。ld
との違いはよくわかりません…。
その他の情報
$ g++ -save-temps -std=c++11 hello.cpp
とすると、上記でステップバイステップで生成した中間ファイル hello.ii
, hello.s
, hello.o
が一気に生成されます。