1. C/C++ 操作の 4 つのステップ
C/C++ プログラムを作成した後、それを実行するには、前処理、コンパイル、アセンブル、リンクという 4 つの手順を実行する必要があります。以下の図に示すように、各ステップで対応するファイルが生成されます (接尾辞に注意してください)。
セクション 3では、単純な C++ プロジェクトを通じて図のプロセス全体を示し、詳細を説明します。
2. 用語の説明
次のプロセスの導入をよりわかりやすくするために、ここでは C++ コンパイル プロセスに関連するいくつかの一般的な用語について説明します。
2.1 GCC、GNU、gcc及びg++
- GNU: オペレーティング システム。特定の内容は重要ではありません。興味がある場合は、「GCC と GNU の意味とは何ですか?」を参照してください。
- GCC : GNU Compiler Collection (GNU Compiler Collection) の略称。GNU オペレーティング システムのグループ内のコンパイラのコレクションとして理解でき、C、C++、Java、Go、Fortan、Pascal、Objective のコンパイルに使用できます。 -C およびその他の言語。
- gcc : GCC (コンパイラーコレクション) の GNU C コンパイラー (C コンパイラー)
- g++ : GCC (コンパイラーコレクション) の GNU C++ コンパイラー (C++ コンパイラー)
簡単に言うと、gcc は GCC の C コンパイラを呼び出し、g++ は GCC の C++ コンパイラを呼び出します。
*.c
および*.cpp
ファイルの場合、gcc はそれらをそれぞれ c および cpp ファイルとしてコンパイルしますが、g++ はそれらを一律に cpp ファイルとしてコンパイルします。
2.2 コードコンパイルコマンド
gcc/g++ の共通コマンド:
コマンドオプション | 関数 |
---|---|
-E (音符の大文字化) | 指定されたソース ファイルを前処理 (Preprocess) しますが、コンパイル (Compile) は行いません。このステップでは*.i テキスト |
-S (音符の大文字化) | 指定されたソース ファイルをコンパイルしますが、アセンブル (Assemble) は行いません。このステップでは*.s アセンブリ |
-c | 指定されたソース ファイルをコンパイルおよびアセンブルしますが、リンク (Link) は行いません。このステップでは*.o オブジェクト |
-o | 生成されるファイルのファイル名を指定します |
-Iリブ | lib はライブラリ ファイルまたはヘッダ ファイル ディレクトリを意味します。このコマンド オプションは、手動リンク プログラムで呼び出すことができるライブラリ ファイルおよびヘッダ ファイルに使用されます。 |
-std= | -std=c++98、-std=c++11 などのプログラミング言語標準を手動で指定します。 |
2.3 GDB(gdb)
GDB(gdb)の正式名称は「GNUシンボリックデバッガ」で、Linuxでよく使われるプログラムデバッガです。
gdb を使用してデバッグできるようにするには、コードのコンパイル時に gdb を追加する必要があります-g
。
g++ -g -o test test.cpp
この記事では、ソース コードから実行可能バイナリ ファイルを生成するプロセスのみを説明し、現時点ではデバッグ プロセスについては説明しません。デバッグ構成については別の記事で紹介します。
3. C++コンパイル処理の詳細説明
主な参考文献:
このセクションの内容は、次の単純な C++ プロジェクトを使用して実証されます。ファイル構造の例は次のとおりです。
|—— include
|—— func.h
|—— src
|—— func.cpp
|—— main.cpp
その中で、main.cpp
はメインコード、include/func.h
はカスタム関数のヘッダーファイル、 はsrc/func.cpp
関数の具体的な実装です
各ファイルの内容は次のとおりです。
// main.cpp
#include <iostream>
#include "func.h"
using namespace std;
int main(){
int a = 1;
int b = 2;
cout << "a + b = " << sum(a, b) << endl;;
return 0;
}
// func.h
#ifndef FUNC_H
#define FUNC_H
int sum(int a, int b);
#endif
// func.cpp
#include "func.h"
int sum(int a, int b) {
return a + b;
}
3.1 前処理
前処理は、その名前が示すように、コンパイル前の準備作業です。
プリコンパイル#define
では、一部のマクロ定義のテキスト置換が完了し、#include
ファイルの内容が.cpp
ファイルにコピーされます。ファイル内にファイルが.h
ある.h
場合は、再帰的に展開されます。前処理ステップでは、コードのコメントは直接無視され、後続の処理には入らないため、プログラム内でコメントは実行されません。
gcc/g++ の前処理は基本的にプリプロセッサcpp
(c preprocess の略語でしょうか?) を通じて行われるため、コマンドを渡すg++ -E
か前処理することができます。cpp
main.cpp
g++ -E -I include/ main.cpp -o main.i
# 或者直接调用 cpp 命令
cpp -I include/ main.cpp -o main.i
上記のコマンドでは次のようになります。
g++ -E
cpp
前処理後にコンパイラをその後のコンパイル処理を行わずに終了させるという命令に相当します。-I include/
ヘッダーファイルのディレクトリを指定するために使用されますmain.cpp
前処理するソースファイルです-o main.i
生成されるファイル名を指定するために使用されます
前処理後のプログラムの形式は*.i
テキスト ファイルのままであり、任意のテキスト エディタで開くことができます。
前処理後のファイル構造は次のようになります。
|—— include
|—— func.h
|—— src
|—— func.cpp
|—— main.cpp
|—— main.i
3.2 コンパイル
コンパイルは、作成したコードを アセンブリ コード に変換するだけです。その仕事は、語彙や文法規則をチェックすることです。したがって、プログラムに語彙や文法上のエラーがなければ、ロジックがどれほど間違っていても、エラーは報告されません。
コンパイルとは、ソースファイルからバイナリプログラムに至るまでのプログラム全体のプロセスを指すのではなく、前処理されたプログラムを特定のアセンブリコード(アセンブリコード)に変換するプロセスを指します。
コンパイルされた命令は次のとおりです。
g++ -S -I include/ main.cpp -o main.s
前処理と同様に、上記のコマンドでは次のようになります。
g++ -S
処理を行わずにコンパイル後にコンパイラを停止させることです。-I include/
ヘッダーファイルのディレクトリを指定するために使用されますmain.cpp
コンパイルするソースファイルです-o main.s
生成されるファイル名を指定するために使用されます
コンパイルが完了すると、プログラムのアセンブリ コードmain.s
が生成されます。これはテキスト ファイルでもあり、任意のテキスト エディタで直接表示できます。
実行後のファイル構成は以下のようになります。
|—— include
|—— func.h
|—— src
|—— func.cpp
|—— main.cpp
|—— main.i
|—— main.s
3.3 組み立て(組み立て)
アセンブリプロセスでは、前ステップのアセンブリコード(
main.s
)を機械語(マシンコード)に変換します。このステップで生成されるバイナリ形式のファイルはオブジェクトファイル(main.o
)と呼ばれます。
gcc/g++ のアセンブリ プロセスはas
コマンドため、g++ -c
またはas
コマンドによってアセンブリを完了できます。
g++ -c -I include/ main.cpp -o main.o
# 或者直接调用 as 命令
as main.s -o main.o
上記のコマンドでは次のようになります。
g++ -c
アセンブル後にコンパイラを終了させます。これは以下と同等です。as
-I include/
ヘッダー ファイルのディレクトリを指定するために引き続き使用されますmain.cpp
アセンブルするソースファイルです-o main.o
生成されるファイル名を指定するために使用されます
アセンブリ手順では、ソース ファイルごとにオブジェクト ファイルを生成する必要があります (この記事のサンプル コードではmain.cpp
、 )。func.cpp
したがって、ファイルfunc.cpp
を生成するには、このアセンブリ プロセスを 1 回実行する必要もありますfunc.o
。
# 可以用 g++ -c 命令一步生成 func.o
g++ -c -I include/ src/func.cpp -o src/func.o
# 当然也可以按照上面的预处理、编译、汇编三个步骤生成func.o
この時点で、コードのファイル構造は次のようになります。
|—— include
|—— func.h
|—— src
|—— func.cpp
|—— func.o
|—— main.cpp
|—— main.i
|—— main.s
|—— main.o
3.4 リンク
C/C++ コードがアセンブルされた後に生成されるオブジェクト ファイル ( *.o
) は、最終的な実行可能バイナリ ファイルではなく、依然として中間ファイル (または一時ファイル) であり、実行可能ドキュメントになるにはオブジェクト ファイルをリンク (Link) する必要があります。
オブジェクト ファイルと実行可能ファイルの形式は同じ (両方ともバイナリ形式) のに、なぜ再度リンクするのでしょうか?
コンパイルでは作成したコードをバイナリ形式に変換するだけなので、プログラムの実行に必要なシステム コンポーネント (標準ライブラリ、ダイナミック リンク ライブラリなど) と組み合わせる必要もあります。
リンク (Link) は、実際には、すべてのバイナリ形式のオブジェクト ファイル (.o) とシステム コンポーネントを実行可能ファイルに結合する「パッケージ化」プロセスです。リンクを完了するプロセスには、リンカー (Linker)と呼ばれる特別なソフトウェアも必要です。
さらに、C++ プログラムをコンパイルする場合、
.cpp
ファイルのみが認識され、各 cpp ファイルは 1 回コンパイルされてファイルが生成されることに注意してください.o
。このとき、リンカはオブジェクトファイルとシステムコンポーネントを結合するだけでなく、コンパイラが生成した複数の.o
orファイル.obj
を結合して最終的な実行ファイル(Executable file)を生成する必要があります。
この記事のコードを例として、func.o
と をmain.o
実行可能ファイルにリンクしますmain.out
。手順は次のとおりです。
g++ src/func.o main.o -o main.out
-o main.out
生成された実行可能バイナリの名前を指定するために使用されます。- システムコンポーネントは自動的にリンクされるため、
g++
カスタム関数のオブジェクトファイルをmain.o
リンクするだけで済みます。
を実行するmain.out
と、結果は次のようになります。
./main.out
a + b = 3
3.5 概要
上記の紹介からわかるように、C++ ソース コードから最終的な実行可能ファイルまでの中間プロセスは単純ではありません。前処理、コンパイル、アセンブル、リンクの各ステップの役割を理解すると、より複雑なプロジェクトに対処するのに役立ちます。
.cpp
しかし、そのような面倒なコンパイルプロセスに思いとどまる必要はありません。単純なコードをコンパイルすると、
// hello.cpp
#include <iostream>
using namespace std;
int main(){
cout << "Hello, world!" << endl;
return 0;
}
g++
中間プロセスを気にせずに、コマンドを使用して実行可能ファイルを直接生成することも可能です。
g++ hello.cpp -o hello
./hello
Hello, world!