一直对编译这个事情不是太明白,想好好学习一下,今天自己写东西试验,遇到了这么个问题,想请教一下,先贴代码
// test.h
#ifndef JUST_FOR_TEST_H_
#define JUST_FOR_TEST_H_
struct Test {
static int i;
void t();
};
int Test::i = 0;
#endif
// test.cpp
#include "test.h"
void Test::t() { }
// main.cpp
#include "test.h"
int main() {
Test t;
t.t();
}
本来觉得这个是很简单的,但是链接的时候报了错,提示是
ld: 1 duplicate symbol for architecture x86_64
将test.h中静态成员的定义放到cpp文件中就好了,但是我很不理解,不是使用了头文件保护吗?这样不是已经确保了一个头文件在一个工程中只会被包含一次吗?是我哪里的理解出了偏差了吗?希望各位高手可以指点一下,本人菜鸡,不是学计算机出身的,所以问的问题可能很傻,如果可以的话希望可以讲的详细一点,如果可以介绍两本学习的书就更好了,先谢过了!
短い答え:
ヘッドガードは、ヘッダー ファイルが 2 回インクルードされることを防ぐことしかできません。コンパイル時の問題は、i が異なるコンパイル単位で定義されていることです。
ほとんどの IDE がコンパイルされるとき、すべてのソース ファイルは個別にターゲット ファイルにコンパイルされ、最後にターゲット ファイルはコネクタによって接続されます。ターゲットファイル間の接続関係は、対応するソースファイルのインクルードから推測されます。ヘッダー ファイルはコンパイルに参加しません。ここでIDEが生成するコンパイルスクリプトはほぼ
clang++ main.cpp test.cpp
です。もちろん、プロジェクト設定には多くのコンパイル オプションがあります。この例では、コンパイラーは test.cpp と main.cpp をコンパイルしてターゲット ファイルを生成します。次に、コネクタは 2 つのターゲット ファイルを結合します。静的メンバー変数 i はヘッダー ファイルで定義されているため、両方のソース ファイルにこのヘッダー ファイルがインクルードされます。結果として、i は 2 つのコンパイル単位に 1 回定義されます。最後に接続すると接続エラーが発生します。
長い答え:
あなたが発生したリンク エラーは、実際には ODR 違反によって引き起こされています。
ODR 契約:
しかし、これ以外にも多くの特殊なケースがあります。ここではそれらをリストしません(すべてをリストすることはできないためです...)。
翻訳単位、定義、プログラムという 3 つの概念が非常に重要です。
翻訳単位 は、コンパイル命令によって決定されるいくつかのコンパイル単位があります。コンパイル単位のコンパイル結果をオブジェクトファイルと呼びます。 定義 は定義です。この例では、
int Test::i = 0;
は静的メンバー変数i
の定義です。ODR によれば、この定義はプログラム全体で 1 回だけ出現します。 プログラム は、多くのターゲット ファイルによってリンクされているプログラムです。コンパイル手順:
clang++ main.cpp test.cpp -o main
このコンパイルには、main.cpp と test.cpp の 2 つのコンパイル単位があります。これらは個別にターゲット ファイルにコンパイルされます。
リーリー#include
はプリコンパイルされたマクロで、対応するファイルの内容を導入し、すべてのプリコンパイルされた命令はソース コードがコンパイルされる前に実行されます。つまり、コンパイル単位「test.cpp」(または Clang コンパイラーが受け取るコード)の内容は次のとおりです。コンパイル単位「main.cpp」の内容は次のとおりです:
リーリーすべてのマクロが実行され、コメントはコンパイラによって無視されます。
これら 2 つのコンパイル単位は、それぞれ test.o や main.o などのターゲット ファイルにコンパイルされます。コンパイル作業が完了しました。次に、コンパイラは 2 つのオブジェクト ファイル test.o と main.o を 1 つのプログラムにリンクします このリンク中に ODR 違反が発生します 。
test.cpp で
int Test::i = 0;
が定義されている場合。そうすれば、この定義は main.cpp に表示されなくなり、ODR に違反することはなくなります。