Axiuの学習ノートを参照
テストコード
#include<iostream>
using namespace std;
#define PI 3.14
int main(){
//测试代码
cout<<PI<<endl;
cout<<"hello world"<<endl;
return 0;
}
前処理
処理内容
- 定義を削除し、すべてのマクロ定義を展開します
- 「#if」、「#endif」、「#ifdef」、「#elif」、「#else」など、すべての条件付きプリコンパイル済みディレクティブを処理します。
- 「#include」プリコンパイル済みディレクティブは、ファイルの内容をその場所に置き換えるために処理されます。このプロセスは再帰的であり、ファイルには他のファイルが含まれています。
- すべてのコメント「//」と「/**/」を削除します。
- すべての #pragma コンパイラ ディレクティブを保持します。コンパイラは、次のようなそれらを使用する必要があります。 #pragma Once は、ファイルが繰り返し参照されるのを防ぐためです。
- 行番号とファイル ID を追加すると、コンパイラはコンパイル時にデバッグ用の行番号情報を生成したり、コンパイル中にコンパイル エラーや警告が生成されたときに行番号を表示したりできます。
例
g++ main.cpp -E -o main.i
- -E: 前処理を示します
- -o はターゲットファイルを示します
......
# 2 "main.cpp" 2
# 2 "main.cpp"
using namespace std;
int main(){
cout<<3.14<<endl;
cout<<"hello world"<<endl;
return 0;
}
コンパイル
コンパイル
プリコンパイル後に生成された xxx.i または xxx.ii ファイルに対して一連の字句解析、構文解析、意味解析、および最適化を実行して、対応するアセンブリ コード ファイルを生成します。
- 字句解析: 「有限状態マシン」に似たアルゴリズムを使用して、ソース コード プログラムがスキャナーに入力され、その中の文字シーケンスが一連のトークンに分割されます。
- 構文分析: 構文アナライザーは、スキャナーによって生成されたトークンに対して構文分析を実行して、構文ツリーを生成します。パーサーによって出力される構文ツリーは、式をノードとして含むツリーです。
- 意味分析: 文法アナライザーは式の文法レベルの分析のみを完了し、意味アナライザーは式が意味があるかどうかを判断します。動的セマンティクスは、実行時にのみ決定できるセマンティクスです。
- 最適化: ソースコードレベルでの最適化プロセス。
- オブジェクト コードの生成: 中間コードはコード ジェネレーターによってオブジェクト マシン コードに変換され、一連のコード シーケンス (アセンブリ言語表現) が生成されます。
- ターゲット コードの最適化: ターゲット コード オプティマイザーは、適切なアドレッシング モードを見つけたり、ディスプレースメントを使用して乗算を置き換えたり、冗長な命令を削除したりするなど、上記のターゲット マシン コードを最適化します。
例
g++ main.i -S -o main.s
- -S はコンパイルを意味します
.file "main.cpp"
.text
.section .rodata
.type _ZStL19piecewise_construct, @object
.size _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:
.zero 1
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.LC1:
.string "hello world"
.text
.globl main
.type main, @function
main:
.LFB1493:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq .LC0(%rip), %rax
movq %rax, -8(%rbp)
movsd -8(%rbp), %xmm0
leaq _ZSt4cout(%rip), %rdi
call _ZNSolsEd@PLT
movq %rax, %rdx
movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rax
movq %rax, %rsi
movq %rdx, %rdi
call _ZNSolsEPFRSoS_E@PLT
leaq .LC1(%rip), %rsi
leaq _ZSt4cout(%rip), %rdi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
movq %rax, %rdx
movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rax
movq %rax, %rsi
movq %rdx, %rdi
call _ZNSolsEPFRSoS_E@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1493:
.size main, .-main
.type _Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB1983:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
cmpl $1, -4(%rbp)
jne .L5
cmpl $65535, -8(%rbp)
jne .L5
leaq _ZStL8__ioinit(%rip), %rdi
call _ZNSt8ios_base4InitC1Ev@PLT
leaq __dso_handle(%rip), %rdx
leaq _ZStL8__ioinit(%rip), %rsi
movq _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax
movq %rax, %rdi
call __cxa_atexit@PLT
.L5:
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1983:
.size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1984:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $65535, %esi
movl $1, %edi
call _Z41__static_initialization_and_destruction_0ii
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1984:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.section .rodata
.align 8
.LC0:
.long 1374389535
.long 1074339512
.hidden __dso_handle
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
編集
アセンブリ コードをマシン実行可能命令 (マシン コード ファイル) に変換します。アセンブラのアセンブル処理はコンパイラに比べて単純であり、複雑な構文、セマンティクス、命令の最適化は必要なく、アセンブリ命令と機械語命令の対比表に従って一つずつ翻訳するだけです。アセンブリプロセスには、アセンブラが完了しています。コンパイル後、オブジェクトファイル(実行ファイルとほぼ同じ形式)xxx.o(Linuxの場合)、xxx.obj(Windowsの場合)が生成されます。
g++ main.s -s -o main.o
リンク
さまざまなソース ファイルによって生成されたオブジェクト ファイルをリンクして、実行可能プログラムを形成します。
静的リンク
実行可能ファイルをコンパイルおよびリンクする場合、リンカーはこれらの関数とデータをライブラリからコピーし、参照されるプログラムの他のモジュールと結合して、最終的な実行可能ファイルを作成します。リンカは主に次の 2 つのタスクを実行します。
- シンボル解決: 各シンボルは関数、グローバル変数、または静的変数に対応します。
- 再配置: リンカは、各シンボル定義をメモリ位置に関連付けることによって機能し、その後、それらのシンボルへのすべての参照を変更して、シンボルがそのメモリ位置を指すようにします。
欠点がある
スペースの無駄です。各実行可能プログラムにはすべてのオブジェクト ファイルのコピーが必要です。そのため、複数のプログラムが同じオブジェクト ファイルに依存している場合、同じオブジェクト ファイルが表示されます。
アドバンテージ
プログラムの実行に必要なものがすべて実行可能ファイルにすでに存在しているため、高速に実行されます。
ダイナミックリンク
プログラムをモジュールごとに比較的独立した部分に分割し、実行時にそれらをリンクします。Linux は .so 形式、Windows は dll ファイルです。== メモリ内では、共有ライブラリの .text セクション (コンパイルされたプログラムのマシン コード) の 1 つのコピーを、実行中のさまざまなプロセスで共有できます。
欠点がある
- パフォーマンスの低下。プログラムが実行されるたびにリンクする必要があるため、一定のパフォーマンスの低下が発生します。
- 対応するランタイム ライブラリがコンピュータにインストールされていない場合、動的にコンパイルされた実行可能ファイルは実行できません。
アドバンテージ
- 共有ライブラリ: 複数のプログラムが実行時に同じコピーを共有します。
- 更新が簡単: 更新時に元のターゲット ファイルを置き換えるだけで、次回プログラムを実行するときに、新しいバージョンのターゲット ファイルが自動的にメモリにロードされてリンクされます。