C++コンパイルリンクの些細なこと

5772 ワード

最近、同僚からC++のリンクのコンパイルに関する質問が何度もありました.以下のような質問があります.
1:どのような関数と変数をヘッダファイルに定義できますか
2:extern「C」の役割
3:重複除外マクロの役割
4:関数間のリンク
これらの問題は難しくないと思います.本には基本的にありますが、本当に考えたことがなければ、丸暗記します.つまり、「口で言う」しかありません.問題に直面するのは本当に難しいので、言う必要があると思います.
C/C++のコンパイルリンクプロセス
実は、「コンパイル」という言葉は多くの場合、私たちはたくさんのことを指しています.h,.c,.cppファイルはリンクライブラリまたは実行可能ファイルを生成するプロセスです.しかし、C/C++にとって、これはあいまいで、C/C++ファイルの山からアプリケーションを生成するには、前処理---ファイルのコンパイル---リンク(本明細書の論述に影響を与えない粗雑な書き込み)が含まれています.
まず、コンパイルユニットとは何かを理解するには、1つのコンパイルユニットが1つであると考えることができる.cまたは.cppファイルは、各コンパイルユニットがまず前処理により一時的なコンパイルユニットを得る、ここでtmpと呼ぶ.cpp,前処理は.cまたは.cppが直接または間接的に含む他のファイル(局は.hファイルに限らず、#includeであればよい)の内容を置き換え、マクロ呼び出しなどを展開する.
まず例を見てみましょう.
a.h
#ifndef A_H_
#define A_H_                                                                                                          
                                                                                                                      
static int a = 1;
void fun();                                                                                                           

#endif
a.cpp
#include "a.h"


static void hello_world()
{
}
は、a.hとa.cppの2つのファイルのみで、簡単です.まずg++の−Eパラメータによりa.cpp前処理後の内容を得る
coderchen@coderchen:~/c++$ g++ -E a.cpp > tmp.cpp
tmpを表示する.cpp
# 1 "a.cpp"
# 1 ""
# 1 ""
# 1 "a.cpp"
# 1 "a.h" 1



static int a = 1;
void fun();
# 2 "a.cpp" 2


static void hello_world()
{
}
tmp.cppは前処理のみで得られるファイルであり,このファイルこそコンパイラが本当に見ることができるファイルである.この過程は
前処理
そのうち#define A_H_a.hというヘッダファイルが重複することを防止する役割を果たしています.多くの人が知っていますが、よく聞いてみると、多くの人がはっきり言えないのを見たことがあります.
このマクロは、1つのコンパイルユニット(cppファイル)が同じヘッダファイルを繰り返し含むことを防止するためである.前処理段階で機能し、前処理器はa.cpp内でA_が定義されていることを発見した.H_このマクロでは、a.cppで再び#include"a.h"を発見した場合、a.hの内容をa.cppに置き換えることはありません.コンパイラはtmpを見た.cppの場合、objファイルにコンパイルされ、最後にリンクによってobjファイルにリンクされ、実行可能プログラムが得られます.
コンパイルエラーと接続エラー
コンパイルエラーとは、cppコンパイルユニットがコンパイル時に発生するエラーを指します.このエラーは一般的に文法エラー、スペルエラー、パラメータの不一致などです.
main.cppを例に(main関数が1つしかない)
int main()                                                                                                            
{                                                                                                                     
  hello_world();                                                                                                      
}    
コンパイル(プラス-cパラメータはコンパイルのみリンクしないことを示す)
coderchen@coderchen:~/c++$ g++ -c -o main.o main.cpp
main.cpp: In function ‘int main()’:
main.cpp:4: error: ‘hello_world’ was not declared in this scope
このエラーはコンパイルですhello_world関数は宣言されていません.
void hello_world();この文はmain関数の前に追加され、再コンパイルされます.
coderchen@coderchen:~/c++$ g++ -c -o main.o main.cpp
coderchen@coderchen:~/c++$ 
コンパイルに成功しました.helloを呼び出しましたが.world関数ですが、この関数は定義されていません.では、これをoファイルリンクの下で、
coderchen@coderchen:~/c++$ g++ -o main main.o
main.o: In function `main':
main.cpp:(.text+0x7): undefined reference to `hello_world()'
collect2: ld returned 1 exit status
見たでしょう、リンクldはリンクエラーを報告しました.理由はhelloです.worldという関数は見つかりません.この例は簡単で,コンパイルエラーとリンクエラーを基本的に区別できる.もう一つhelloを追加しますworld.cpp
void hello_world()
{ 
}
コンパイル
coderchen@coderchen:~/c++$ g++ -c -o hello_world.o hello_world.cpp
リンク
coderchen@coderchen:~/c++   $ g++ -o main main.o hello_world.o
ok、私たちのmainプログラムはすでに生成されて、私たちは前処理---コンパイル---リンクの過程を経験しました.
なぜhelloを書く必要がないのかという人もいます.world.hのヘッダファイル、hello_を宣言world関数、それからmain.cppはhelloを含むworld.hは?このように書くのはもちろん標準的な方法ですが、前処理後、私たちが今書いたのと同じように、前処理はhello_world.hの内容をmainに置き換える.cpp中.
質問:リンクするときmain.oどうしてhelloを知ってるの?world関数定義はhello_world.o中は?
答え:main.o知らなかったhello_world関数はそのobjファイルに定義され、各objファイルにはエクスポートシンボルテーブルがあり、この例ではhello_world.oのエクスポートシンボルテーブルにhello_がありますworldという関数、main.oこの関数を使う必要があり、いくつかのスロットのように想像できます.リンクはobjファイルをスキャンすることによってこの関数定義がhello_にあることを発見した.world.oで、それからリンクできます.
質問:なぜ関数がヘッダファイルに定義できないのですか?
この問題は、inlineとstaticで修飾された関数がヘッダファイルに定義され、inlineで修飾された関数がヘッダファイルに定義されなければならないため、不適切である.
関数がヘッダファイルに定義され、複数のcppファイルにこのヘッダファイルが含まれている場合、これらのcppファイルで生成されたobjファイルのエクスポートシンボルテーブルには、このヘッダファイルで定義された関数があり、単一ファイルのコンパイル時にはエラーはありませんが、リンク時にエラーが発生します.リンクは複数の関数エンティティを検出しましたが、どちらを使用するべきかは分かりません.これはリンクエラーです.
inline修飾の関数は、通常、関数エンティティは存在しません.コンパイラがインラインしていなくても、objファイルはinline関数をエクスポートしません.したがって、リンクにエラーはありません.
static修飾の関数は、定義されたコンパイルユニットによってのみ呼び出され、エクスポートされません.ヘッダファイルの上のstatic修飾関数があれば、複数のobjファイルの中で同じ関数が1つずつ付いていることに相当し、みんながそれぞれ使って、干渉を補完します.
質問:ヘッダファイルにどのような変数を定義できますか?
実際には変数は関数と似ており、staticまたはconstで修飾された変数はヘッダファイルで定義できます.
static修飾の変数はstatic修飾の関数と同じで,道理は同じである.
const修飾の変数はデフォルトではエクスポートシンボルテーブルには入りません.objごとに同じconst変数が定義されていることに相当し、それぞれが使用されます.constはexternで修飾できますが、extern constで修飾された変数をヘッダファイルに定義すると、「externが何をしているのか考えてみよう」とリンクエラーが発生します.
質問:extern「C」は何ですか?
もし誰かが「CとC+++の互換性」と答えたら、私は「これは正しい答えですが、私はあなたが本当に知っているかどうか分かりません」としか言えません.
まず、Cがリロードをサポートしていないこと、C++がリロードをサポートしていること、C++がリロードをサポートするために関数の名前を変更するメカニズムが導入されていることを知っておく必要があります.以下のようにします.
int hello_world(type1 param);
int hello_world(type2 param);
通常、最初の関数はhello_にコンパイルされます.world_type 1のように、2番目の関数はhello_にコンパイルされます.world_type 2そうです.
定義された場所でも呼び出された場所でも、関数は同じ名前に変更されるので、リンクは関数エンティティを正しく見つけることができます.
C++プログラムを書く場合、通常はcで作成されたライブラリ(gccでコンパイルされたcファイル)が導入されますが、cはリロードをサポートせず、関数の名前を変更することはありません.C++で呼び出された場所の名前が変更される可能性が高いため、呼び出された場所(C++コンパイル)と定義された場所(Cコンパイル)の関数名が一致しない場合があります.これもリンクエラーです.
したがって、C++でextern"C"{#include"some_c.h"}というコードがよく見られます.これがc++コンパイラに伝えるsome_c.hの関数はcでコンパイルし、名前を変更しないでリンクするときにokになります.