main は *.obj ソリューションで既に定義されています。
VC6では、stdafx.hでグローバル変数を定義しようとすると、ヘッダーファイルが複数回インクルードされるため、"already defined in StdAfx.obj "という古典的なエラーがよく発生します。
解決方法 stdafx.cppに変数int g_flagの定義を置き、使用する場所でexternを使用します。CAADlg.cpp で変数 g_flag を使用する場合、CAADlg.cpp の最初の部分、コンストラクタ定義の外側に extern int g_flag を追加してください。
多くの Visual C++ ユーザーが LNK2005:symbol already defined や LNK1169:on に遭遇しています。
ご存知のように、C/C++のソースプログラムから実行ファイルになるまでには、2つの段階があります。(1) コンパイラがソースファイルをアセンブリコードにコンパイルし、アセンブラがマシン命令(およびその他の関連情報)に変換してターゲットファイル(オブジェクトファイル、VCのコンパイラでコンパイルされたターゲットファイルのデフォルトのサフィックスは .obj )に出力する; (2) リンカがオブジェクトファイル(およびおそらくいくつかのライブラリ)を一緒にリンクして完全な実行ファイルを作成する;。
コンパイラはソースファイルをコンパイルし、ソースファイルのグローバルシンボルを強弱をつけてアセンブラに渡し、アセンブラはその強弱情報をエンコードしてターゲットファイルのシンボルテーブルに格納する。では、強弱とは何でしょうか。コンパイラは関数や初期化されたグローバル変数を強いシンボルとみなし、初期化されていないグローバル変数は弱いシンボルになります。たとえば、次のようなソースファイルがあったとする。
extern int errorno;
int buf[2] = {1,2};
int *p;
int main()
{ <未定義
は0を返します。
}
ここで、main と buf は強いシンボル、p は弱いシンボル、errorno は外部変数の使用宣言に過ぎないので、強くもなく弱くもなくである。
強シンボルと弱シンボルの概念により、リンカー(Unixプラットフォーム)は以下のルールに従って複数回定義されたグローバルシンボルを処理・選択します([1]、p549〜p550参照)。
ルール1:強シンボルは複数回定義できない(つまり、異なるターゲットファイルに同名の強シンボルを定義してはならない)。
ルール2:あるシンボルが1つのターゲットファイルで強く、他のすべてのファイルで弱い場合、強いシンボルを選択する。
ルール3:あるシンボルがすべてのターゲットファイルで弱い場合、そのうちのどれかを選択する。
上記3つのルールはUnixプラットフォームのリンカーのものであるが、筆者らの実験によると、少なくともVC6.0のリンカーもこのルールに則っているとのことである。つまり、複数のターゲットファイルでは、同じ名前の関数や初期化されたグローバル変数を重複して使用できないため、LNK2005とLNK1169という2つのリンクエラーになります。しかし、自分のプログラムではそのような再定義が見つからないのに、このようなリンクエラーに遭遇することがありますが、これはどうしたら解決できるのでしょうか。さて、問題はもう少し複雑なので、ゆっくり説明しましょう。
ご存知のように、ANSI C/C++ では非常に多くの標準関数が定義されており、それらは多くの異なるターゲットファイルに分散されているため、もしそれらがターゲットファイルとしてプログラマに直接提供されると、どの関数がどのターゲットファイルに存在するかを正確に把握し、リンク時にターゲットファイル名を明示的に指定して実行ファイルをうまく生成しなければならず、明らかに大きな負担となります。そこでC言語では、複数のターゲットファイルを1つのファイル(スタティックライブラリ)にパッケージングする機構を用意している。開発者はリンク時にライブラリのファイル名を指定するだけで、リンカーは自動的にライブラリの中からアプリケーションが使用するターゲットモジュールを探し出し、実行ファイルの構築に参加するためにライブラリからコピーする(コピーするだけ)のです。ほとんどすべてのC/C++開発システムは、開発者が利用できるように標準関数を標準ライブラリにパッケージしている(そうでないことはない?
ライブラリは開発者にとって便利なものですが、混乱の元にもなっています。リンカ(Unixプラットフォーム)がライブラリへの参照を解決する方法を見てみましょう([1]、p556参照)。
シンボル解決の段階で、リンカーはすべてのターゲットファイルとライブラリファイルを、コマンドラインに現れた順に左から右へスキャンし、その間、いくつかのセットを維持します。(1) 集合Eは、実行ファイルを形成するためにマージされるすべてのターゲットファイルの集合です。 (2) 集合Dは、Eに追加されたターゲットファイルによって以前に定義されたすべてのシンボルの集合です。 (3) 集合Uは、未解決シンボル(すなわち、E内のターゲットファイルによって参照されているがD内にまだ存在しないシンボル)の集合です。はじめはE,D,Uはすべて空である。
(1): コマンドラインの各入力ファイル f に対して、リンカはそれがターゲット・ファイルかライブラリ・ファイルかを判断する。ターゲット・ファイルであれば、fをEに加え、fの未解決シンボルと定義済みシンボルをそれぞれUとDの集合に加え、次の入力ファイルを処理する。
(2): f がライブラリファイルの場合、リンカは U の未解決シンボルをすべて f の各ターゲットモジュールで定義されたシンボルとマッチングさせようとする。ターゲットモジュール m が U の未解決シンボルを定義している場合、m を E に追加し、m の未解決シンボルと定義されたシンボルをそれぞれ U および D のセットに追加する。この処理をfのすべての対象モジュールについて、ある定点に達するまで連続的に繰り返し、その時点でUとDはもはや変化しなくなる。Eに追加されなかったfの対象モジュールは単に破棄され、リンカーは次の入力ファイルに移る。
(3): すべての入力ファイルをスキャンしたとき、Uが空でない場合、またはDに同じ名前のシンボルが複数回追加されている場合、リンカーはエラーメッセージを報告し、終了する。それ以外の場合は、Eにあるターゲットファイルをすべてマージして実行ファイルを生成する。
上記のルールはUnixプラットフォームのリンカですが、VC(少なくともVC6.0)のリンカは全く異なり、まずコマンドラインに現れたターゲットファイルを順番に処理し、Uが空になるまで、あるいはスキャン中にUとDに変化がないある時間(最初から最後まで順番にすべてのライブラリファイルをスキャンすることを1回呼び出す)まではすべてのライブラリファイルを順にスキャンしていくのだそうです。このとき、Uが空かどうか、Dに同じ名前のシンボルが繰り返し追加されているかどうかによって、エラーで終了するか実行ファイルを生成するかを決定する。Unixリンカは入力ファイルがコマンドラインに現れる順序に敏感であり、VCのアルゴリズムはリンクにおけるファイル順序の影響を最小限にすることが明らかである。Unix上の新しい開発ツールが対応する実務を改善したかどうかは筆者らには不明であり、実務経験者はこの点に関する情報の追加を歓迎する(2005年10月10日追記。MinGW 3.1.0をgcc 3.2.3でリンカしたところ,参考文献[1]にあるような挙動を示すことが確認できた.)
VCに付属するコンパイラはcl.exeで、標準ライブラリに関連するオプションは以下の通りです。/ML, /MLd, /MT, /MTd, /MD, /MDd. これらのオプションは、アプリケーションが使用したいC標準ライブラリのバージョンをコンパイラに伝えるものです。/ML (デフォルト・オプション) はシングルスレッド・スタティック・バージョンの標準ライブラリ (libc.lib) に対応し、/MT はマルチスレッド・スタティック・バージョンの標準ライブラリ (libcmt.lib) に対応し、コンパイラが自動的に _MT マクロを定義します。/MD はマルチスレッド・ DLL バージョン(輸入ライブラリ msvcrt.lib, DLL は msvcrt.dll) に対応し、コンパイラが _MT と _DLL 2 つのマクロを自動的に定義します。d の後にオプションが続くと、コンパイラは自動的に追加の _DEBUG マクロを定義し、対応する標準ライブラリのデバッグ版を使用することを示します。したがって、/MLd はシングルスレッド静的標準ライブラリのデバッグ版 (libcd.) に相当します。 lib)、/MTd はマルチスレッドのスタティック標準ライブラリ(libcmtd.lib)のデバッグ版に対応し、/MDd はマルチスレッドの DLL 標準ライブラリ(import library msvcrtd.lib, DLL は msvcrtd.dll) のデバッグ版に対応するように定義されています。コンパイル時にアプリケーションが使用したい標準ライブラリのバージョンをコンパイラに正確に伝えますが、コンパイラの作業が終わった後、リンカが作業する番が来たときに、ターゲット・ファイルの行方不明者をどうやって知るのでしょうか。愛情を伝えるために、私たちのコンパイラは少し秘密の仕事をしています。でcliledターゲットファイルは、特別な領域を持っている(最終的な友人でファイルのどこにCOFFとPEファイル形式を参照することができます)どのように動作するようにリンカを導くためにいくつかの情報を格納するために、それらの1つは、ライブラリファイル名の数を指定し、デフォルトライブラリ(デフォルトライブラリ)と呼ばれるリンカがターゲットファイルをスキャンすると、それはターゲットモジュールに現れる順番にこれらのライブラリ名の処理が行われます。もしそのライブラリが現在の入力ファイルリストに存在しなければ、入力ファイルリストの末尾に追加され、そうでなければ省略されます。そういえば、ちょっとした実験をしてみよう。トップシンプルのプログラムを書き、main.c という名前で保存する。
/* main.c */
int main() { return 0; }.
次のコマンドでmain.cをコンパイルしてください(えっ、あなたはプログラムをコンパイルするのにコマンドラインを使ったことがないのですか? これは......).
cl /c main.c
/c は cl にソースファイルのみをコンパイルし、リンクはしないように指示しています。MLはデフォルトのオプションなので、上のコマンドは次のコマンドと同じです: cl /c /ML main.c . もし何も問題がなければ(そして、それはもっとひどいことになります!) もちろん、環境変数が正しく設定されていない場合は別ですが、その場合はVCのbinディレクトリに移動してvcvars32.batファイルを見つけて実行してください)。カレントディレクトリに、main.obj ファイルが現れます。これをテキストエディタで開き(そう、テキストエディタです、大胆に、恐れず)、"defaultlib" という文字列を探せば、たいてい次のようなものが見つかるでしょう。ああ、そうだ、その通りだ
は、ターゲットファイルに保存されているデフォルトのライブラリ情報です。このターゲットファイルでは、標準ライブラリ libc.lib のシングルスレッド静的バージョン (/ML オプションと一致) と oldnames.lib (Microsoft の以前の C/C++ 開発システムと互換性があることが目的で、ほとんど使用されていないので、議論を簡単にするために無視して構いません) の 2 つのデフォルトライブラリを指定しているようです。また、ソース・プログラムが
/* xxxxは実際のライブラリファイル名 */
#pragma comment(lib,"xxxx")
コンパイラ指令コマンドでリンクするライブラリを指定すると、その情報はターゲットファイル内のデフォルトライブラリ情報エントリにも格納され、デフォルト標準ライブラリの前に位置します。このようなコマンドが複数ある場合、対応するライブラリ名はソースプログラムに現れるのと全く同じ順序でターゲットファイルに現れます(そして、すべてデフォルトの標準ライブラリの前に位置します)。
VCのリンカはlink.exeで、main.objがデフォルトのライブラリ情報を持っているので、それを利用することができます
link main.obj libc.lib
または
リンク main.obj
を実行して実行ファイルmain.exeを生成する場合、この2つのコマンドは等価です。しかし、もし
リンク main.obj libcd.lib
リンカを使用しない場合、リンカは警告を出します: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library" これは、あなたが明示的に指定した標準ライブラリのバージョンがターゲットファイルのデフォルト値と一致しないことによるものです。一般に、リンカによってマージされるすべてのターゲット・ファイルに指定されるデフォルトの標準ライブラリのバージョンが同じであることを確認する必要があります。そうでなければ、コンパイラは上記の警告を必ず出しますが、LNK2005とLNK1169のリンクエラーは出るときと出ないときがあります。では、具体的にどのような時に出るのでしょうか?ああ、心配しないでください、以下はすべて、物事の真相を突き止めるのが好きな人のためだけのものです。
mylib.cというソースファイルを作成し、以下の内容を記述します。
/* mylib.c */
#include <stdio.h>
void foo(void)
{ <未定義
printf("%s","I am from mylib!\n");
使用方法
cl /c /MLd mylib.c
コマンドでコンパイルします。/MLd オプションでデフォルトの標準ライブラリとして libcd.lib を指定していることに注意してください。 lib.exe は VC に付属するコマンドで、ターゲットファイルをライブラリとしてパッケージングするものです。
lib /OUT:my.lib mylib.obj
mylib.objをライブラリとしてパッケージ化し、出力ライブラリファイル名をmy.libとします。
/* main.c */
void foo(void);
int main()
{ <未定義
foo()です。
は0を返します。
}
使用方法
cl /c main.c
コンパイルして、使用する
リンク main.obj my.lib
をリンクしてください。このコマンドは LNK2005 と LNK1169 のリンクエラーを発生させることなく main.exe を正常に生成し、警告メッセージが表示されるだけです: "warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library".この時点でリンカが何をしているのか、前述のスキャンルールに従って分析してみましょう(詳細なリンク処理を見るには/VERBOSEオプションを追加します。ただし、ほとんどすべてのCコンパイラはシンボルの前にアンダースコアを追加してから出力するので、ターゲットファイルやリンク出力で見るシンボルはソースプログラムで見るよりも余計な「_」がついていることに注意してください)。これは非常に重要なポイントである)。.
最初、E,U,Dはすべて空集合です。リンカはまず main.obj をスキャンして、デフォルトの標準ライブラリである libc.lib を入力ファイルリストの最後に追加し、未解決の foo とともに E セットに、そして main を U と D に追加します。 mylib.obj が foo を定義していることがわかったので、それは E に追加され、foo は U から D に転送され、未解決の printf は U に追加され、指定したデフォルト標準ライブラリ libcd.lib は入力ファイルリストの最後 (libc.lib の後) に追加されています。) U と D のどちらも変更されなくなるまで、U のシンボルにマッチする my.lib ライブラリのモジュールを繰り返し実行し続けます。明らかに、そのような不動点に今達しているので、次の入力ファイルがスキャンされ、それはlibc.libです。リンカは printf が libc.lib の printf.obj で定義されていることを発見し、printf は U から D に移動し、printf.obj は E に加えられ、それが定義するすべてのシンボルが D に加えられ、その中の未解決シンボルが U に加えられました。リンク時に/ENTRY(プログラムエントリーポイントオプション)が指定されていない場合、リンカーのデフォルトのエントリーポイントはcrt0.objで定義されている関数mainCRTStartup(GUIプログラムではWinMainCRTStartup)なので、crt0. これらのターゲットモジュールが指定するデフォルトライブラリ(crt0init.obj のみ kernel32.lib を指定)を入力ファイルリストの末尾に追加し、UとDを更新します。libc.lib のモジュールは固定化ポイントに達するまでマッチングが続けられ、次に libcd.lib が追加されますが、この中のターゲットモジュールはどれも U のシンボルを定義していないので、リンカーはこれをスキップして最後の入力ファイルである kernel32.lib へと進みます。実は、Uの中の未解決シンボルと追加すべきシンボルはすべてUで定義されていることがわかるので、kernel32.libの処理が終わったらUは空にしなければならず、リンカーはEすべてのモジュールをマージして実行ファイルを生成します。
ターゲットモジュールごとにデフォルトの標準ライブラリの異なるバージョンを指定してもリンクに成功した前述の例の後、この厳密性の欠如から生じる悲劇的な失敗を目の当たりにすることになる。
mylib.cを次のように修正します。
#include <crtdbg.h>
void foo(void)
{ <未定義
// メモリリークは気にしないでください。
_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ ) を使用します。
}
malloc_dbg は ANSI C の標準ライブラリ関数ではなく、VC 標準ライブラリで提供される malloc のデバッグ版で、開発者が様々なメモリエラーをキャッチできるように関連関数と一緒に使用されます。これを使うには _DEBUG マクロを定義する必要があり、そうしないとプリプロセッサが自動的に malloc に変えてしまいます。 で続けます。
cl /c /MLd mylib.c
lib /OUT:my.lib mylib.obj
コンパイルしてパッケージ化します。で再度コンパイルすると
リンク main.obj my.lib
リンクしてみるとどうでしょうか?なんと、たくさんのLNK2005と、一番下にある高価な"fatal error" LNK1169、そしてもちろんLNK4098です!リンカーがおかしくなったのでしょうか?いいえ、あなたは哀れなリンカーを誤解しているのです。
次に my.lib をスキャンして mylib.obj を E に、libcd.lib を入力ファイルリストの最後に、foo を U に、そして main を D に加えました。次に libc.lib がスキャンされ、その時点で _malloc_dbg は libc.lib のどのターゲットモジュールでも定義されていないことが判明しました (標準ライブラリのデバッグ版にのみ存在します) ので、_malloc_dbg のために E に追加されるモジュールはないでしょう。しかし、libc.lib の crt0.obj はデフォルトのエントリポイント関数 obj を定義しているため、 crt0.obj とそれが直接または間接的に参照しているモジュール (malloc.obj, free.obj など) が E に追加され、これらのターゲットモジュールが指定するデフォルトライブラリ (crt0init.obj のみ kernel32.Debug を指定する) も E に追加されます。 libc.lib のモジュールを固定化ポイントに達するまでマッチングし、libcd.lib を処理した後、dbgheap.obj が _malloc_dbg を定義することがわかったので、 dbgheap.obj を E に、その未解決シンボルを U に、その定義する他のシンボルを D に追加しました。malloc などのシンボルは既に D にあり(libc.lib の malloc.obj で E に追加)、dbgheap.obj やそのために導入された他のモジュールは malloc を含む同じ名前のシンボルをさらに多数定義していて、再定義の衝突を引き起こしてしまうのです。そこでリンカーは、すべての入力ファイルを処理した後(そう、途中で再定義の衝突があってもすべてのファイルを処理し、完全な衝突のリストを生成する)、報告しなければならないのです。このジョブは不可能です。
もうお分かりだと思いますが、リンカーには全く責任はなく、責任は私たちにあるのです。ターゲットファイル(main.obj)とプログラムライブラリ(my.lib)を不注意でリンクしてしまい、デフォルトの標準ライブラリのバージョンが一致しなかったことが、この惨事の原因となったのです。解決策は簡単で、main.cを/MLdオプションで再コンパイルするか、mylib.cを/MLオプションで再コンパイルするか、あるいは単に/NODEFAULTLIB:XXXオプションでデフォルトライブラリXXXを無視してリンクするかですが、この方法は非常に安全ではないので(なぜだと思いますか)お勧めしません。
上記の例では、ライブラリ my.lib のソースコード (mylib.c) があるので、別のオプションで再コンパイルし、再度パッケージングすることができます。しかし、ソースコードが提供されていないサードパーティのライブラリを使用する場合、これらのライブラリに合わせて自作プログラムのコンパイルオプションを変更しなければなりません。しかし、ライブラリ内の対象モジュールが指定するデフォルトのライブラリをどうやって知ることができるのだろうか?実は、VCはこの作業を行うための小さなツールを提供しています。それがdumpbin.exeです。次のコマンドを実行します。
dumpbin /DIRECTIVES my.lib
このようなメッセージには、 "-defaultlib:XXXX" のように、ターゲットモジュールが指定するデフォルトライブラリ名(コンパイル時に /Zl オプションを指定すると、ターゲットモジュールに defaultlib 情報は含まれません)が含まれていることが確認できます。
サードパーティライブラリが指定するデフォルトの標準ライブラリを知り、適切なオプションでアプリケーションをコンパイルすれば、LNK2005とLNK1169のリンクエラーを回避することができます。IDEが好きな人は、"プロジェクトのプロパティ" -> "C/C++" -> "Code Generation(co) を開いてください。
参考にしてください。
[1] コンピュータシステム: プログラマの視点
ランダル・E・ブライアント、デビッド・R・オハラロン著
電子工業出版社、2004年
のように表示される.netのコンパイル問題。
(MSVCR80D.dll) : エラー LNK2005: __CrtDbgReport は、既に libcmtd.lib(dbgrpt.obj) で定義されています。
msvcrtd.lib(MSVCR80D.dll) : エラー LNK2005: _memmove は既に libcmtd.lib(memmove.obj) で定義されています。
に対する解決策は
LNK2005エラー(重複定義エラー)は、プログラミングでよく遭遇するエラーですが、実は解決するのは難しいエラーではありません。なぜできてしまうのか、その原因を突き止めることで簡単に解決することができます。
LNK2005のエラーが発生する主な条件はいくつかあります。
1. グローバル変数が重複して定義されている。2つの可能性があります。
A. プログラミングに不慣れなプログラマーの中には、グローバル変数を使う必要があるところでは、定義を使ってアサートすればいいと考えている人が時々います。これは実は間違っています。グローバル変数は、プロジェクト全体に固有のものです。正しいものは、次のようにCPPファイルで定義する必要があります:int g_Test;その後、CPPファイルで使用する必要があります:extern int g_Testはまだint g_Testを使用する場合、それはLNK2005エラーを生成します、に似た一般的なエラーメッセージです。AAA.objエラーLNK2005 intの本c? book@@3HA 重要なのは、変数に値を代入してもLNK2005エラーになることです。
これは "definition" ではなくて "declaration" でなければならない! C++の標準によると、変数は宣言され、両方の条件を満たす必要があり、そうでない場合は定義されます。
(1) 宣言にはexternキーワードを使用すること (2) 変数に初期値を与えてはいけないこと
というわけで、以下はその宣言です。
extern int a;
を定義しています。
int a; int a = 0; extern int a = 0;
B. プログラミングにそれほど厳密でないプログラマーは、変数を使用する必要があるファイルに必ずグローバル変数を定義し、変数名を考慮しないことで、変数名が重複することも多く、LNK2005のエラーの原因になることがあります。
2. ヘッダーファイルが重複している。多くの場合、ヘッダーファイルは、変数、関数、クラス定義、それらが使用され、複数回含まれている他の場所で、ヘッダーファイルは、関連するマクロと重複リンクを防ぐために他の手段を持っていない場合、それはLNK2005エラーが発生します含める必要がある。解決策としては、インクルードする必要があるヘッダーファイルで同様の処理を行うことです。#ifndef MY_H_FILE // このマクロが定義されていない場合
#define MY_H_FILE //このマクロを定義する
....... //ヘッダーファイルの本体
.......
#endif
上記はマクロを使用していますが、ヘッダーファイルに追記することでプリコンパイルを使用することも可能です。
#プラグマオン
//ヘッダーファイル本体
3. サードパーティライブラリを使用した場合に発生します。主にC言語のランタイムライブラリとMFCのライブラリが競合していることが原因です。解決策としては、エラーを出すライブラリを他のライブラリの前に置くことです。また、別のCライブラリを選択しても、このエラーが発生する場合があります。マイクロソフトとCには2種類のCランタイムライブラリがあり、1つは共通ライブラリです。LIBC.LIBはマルチスレッドに対応していません。もう1つはマルチスレッドをサポートするもの: msvcrt.libです。この2つのライブラリが混在しているプロジェクトでは、このエラーが発生する可能性があります。一般的には、Cランタイムライブラリよりも先にMFCライブラリをリンクする必要があるので、マルチスレッドをサポートしている msvcrt.lib を使うことが推奨されています。したがって、サードパーティ製のライブラリを使う前に、それがどのライブラリにリンクされているかを最初に知っておくべきで、さもないと LNK2005 エラーが発生することがあります。どうしてもサードパーティのライブラリを使いたい場合は、以下の方法で改造してみるのも手ですが、それで問題が解決する保証はありません。
A. VCメニュー Project->Settings->Link->Catagory を選択して Input を選択し、Ignore libraries の編集欄に、無視したいライブラリを次のように記入します。Nafxcwd.lib; Libcmtd.lib.など。その後、オブジェクト/ライブラリモジュールの編集欄に記入し、正しい順序が何であるかを判断できるようにする必要があります。
B. VCメニューのProject->Settings->Link pageを選択し、Project OptionsのEditフィールドに/verbose:libを入力すると、リンクしたプログラムのコンパイル時に出力ウィンドウでリンクの順序を確認することができるようになります。
C. VCメニュー プロジェクト->設定->C/C++ページ、Catagory選択 Co
コンパイラ関連の処理については、こちらをご参照ください。
http://www.donews.net/xzwenlan/archive/2004/12/23/211668.aspx
これらは、私が遭遇したLNK2005のエラーのほんの一部です。このエラーが発生する状況は他にも絶対にあるので、この記事を読んでから何も考えずにエラーのトラブルシューティングをしようとしないでほしいです。プログラミングの作業は考える作業なので、もっと心を開いてください!そうすれば、より多くのものを得ることができます。
付録
コンパイラ処理関連
I. プリプロセッサー・コンパイラー・アセンブラー・リンカー
プリプロセッサーは、関連するプリプロセッシングディレクティブ(通常は "#" で始まるディレクティブ)を処理するものです。例えば #include "xx.h" #define など。
コンパイラは、対応する*.cppを*.sファイル(アセンブリ言語)に変換します。
アセンブラは *.s を処理して、対応する *.o ファイル (obj ターゲットファイル) を生成します。
最後にリンカーは、すべての *.o ファイルを実行ファイル (? .exe) にリンクします。
1. 部品 :
まず知っておくべきことは、コンポーネント(今は狭義のクラスと理解してよい)は、一般にヘッダーファイル(私はインターフェイスと呼びたい、例えば :*.h )と実装ファイル(例えば :*.cpp )に分けられるということです。
ヘッダーファイルには、通常、宣言として使われ、インターフェースとして存在するものを入れます。
実装ファイルは、主に実装に特化したコードです。
2. 個々のファイルをコンパイルします。
IDEは、bulidファイルのときにobjを生成するために実装ファイル(例えば*.cpp)をコンパイルするだけで、vcの下では、? cppとCtrl + F7キーを押して別々にそれをコンパイルすることができますことを覚えておいてください。
対応する?obj ファイルを生成します。cppをコンパイルする際、IDEは? cppに含まれるヘッダーファイルを処理し、#includeでインクルードされたヘッダーファイルを順番に処理します。
(そのヘッダーファイルの中に同じく#includeされているファイルがあれば、個々のヘッダーファイルも順番にフォローするので再帰的)
3. 内部リンクと外部リンク
内部リンクと外部リンクは比較的基本的なことですが、初心者が間違えやすいところでもあるので、ここで詳しく説明しておくことが大切です。
内部リンクによって生成されたシンボルはローカルの ?obj にのみ表示され、外部リンクのシンボルはすべての *.obj 間で表示されます。
例えば、内部リンクは内部リンク、ファイルヘッダで直接宣言された変数、インラインのないグローバル関数は外部リンクとなります。
ファイルヘッダのクラス内部で宣言された関数(関数本体なし)は外部リンクですが、本体付きの関数は一般に内部リンクになります(IDEがインライン関数として扱おうとするため)。
内部リンクと外部リンクを認識する目的は何でしょうか?vc6を使った例です。
// ファイルmain.cppの内容。
void main(){}。
// ファイルt1.cppの内容。
#include "a.h"。
void Test1(){ Foo(); }.
// ファイルt2.cppの内容。
#include "a.h"。
void Test2(){ Foo(); }.
// ファイル a.h の内容。
void Foo( ){ }.
OK、vc で空のコンソールアプリケーションを生成し (File - new - projects - win32 console application)、プリコンパイルオプションをオフにします。
(プロジェクト - 設定 - Cagegoryrecompiled Headers - プリコンパイルされたヘッダを使用しない)
次に、t1.cpp を開き、ctrl+f7 を押してコンパイルし、t1.obj を生成します。
t2.cpp を開き、ctrl+f7 を押してコンパイルし、t2.obj を生成する via
そして、リンクすると、:
リンクする...
t2.obj : error LNK2005: "void __cdecl Foo(void)" ( Foo@@YAXXZ ) t1.obj で既に定義されています。
というのも.
1. t1.cppのコンパイル時に#include "a.h"のFooへの処理で見られるFoo関数プロトタイプ定義は外部リンクなので、t1.objのFooシンボルは外部記録となる。
2. 2. t2.cpp をコンパイルする際、#include "a.h" の Foo への処理で見られる Foo 関数プロトタイプ定義は外部リンクされているので、 t2.obj の Foo シンボルのドキュメント化は外部となります。
3. 最後に、t1.obj と t2.obj をリンクする際、vc は同じ外部シンボルが定義されている 2 つの場所(t1.obj と t2.obj 内)を見つけます(注:定義されています、外部シンボルには、以下のものがあります)。
外部シンボルはグローバルに見えるので、このシンボルを使う t3.cpp の宣言があり、t1.obj が呼び出されるべきことを知らないとすると、このシンボルは複数の場所で定義されることはありません。
またはt2.obj)であるため、エラーが報告されます。
これを解決するには、いくつかの方法があります。
a. a.h の定義を宣言として書き直し、別のファイル a.cpp を使って関数本体を保持する。(ヒント: 代わりに上記のプログラムを試してみてください)
(関数本体は t1.cpp など他の cpp に置くこともできますが、対応する cpp ファイルを使用して格納するのがよいでしょう)。
この時点でa.hを含むファイルには、a.objに加え、関数本体のコードも含まれている
a.h を含む cpp が生成する他のすべての obj ファイルは、対応するシンボルのみで、関数本体はありません。例えば t1.obj や t2.obj はシンボルのみで、最終的にリンクすると IDE は以下のように表示します。
a.obj の Foo() 関数本体が exe ファイルにリンクされ、 t1.obj と t2.obj の Foo シンボルが関数本体の exe ファイル内のアドレスに変換されます。
また:変数がa.hに配置されるとグローバル変数の定義になりますが、どのように宣言になるのでしょうか?
例 a.hに :class CFoo{};CFoo* obj.を追加します。
f7キーを押してビルドすると表示されます。
リンクする...
t2.obj : エラー LNK2005: "クラス CFoo * obj" ( 3PAVCFoo@A ) t1.obj で既に定義されています。
a.cpp でこの変数を定義し ( CFoo* obj ) 、この定義を a.h ファイルにコピーして、extern (extern CFoo* obj) をプレフィックスとして付けるのが良い方法です。
これで合格です。もちろん、extern はその変数を呼び出す前に宣言することもできますが、そうしないことを強くお勧めします。
そのため、インターフェースの整合性が取れなくなる可能性があります。インターフェイスは対応するヘッダファイルに記述するのがよいでしょう.
b. a.h の定義を内部リンクするように修正します。つまり、inline キーワードを追加し、各 t1.obj と t2.obj は Foo 関数本体のコピーを保持しますが、外部リンクはされません。
シンボルなので、他のobjファイルから参照されることはなく、競合することはありません。(ヒント: 代わりに上記のプログラムを試してみてください)
また、"vcがobjファイルに外部シンボルのフラグを記録する"ことを検証する実験もしてみました(ちょっと毒舌)。下記でご覧いただけます。
(1)ファイルの中身です。
// ファイルmain.cppの内容。
void main(){}。
// ファイルt1.cppの内容。
#include "a.h"。
void Test1(){ Foo(); }.
// ファイルt2.cppの内容。
#include "a.h"。
void Test2(){ Foo(); }.
// ファイル a.h の内容。
inline void Foo( ) { }.
(2) t1.cpp を選択して ctrl+f7 で別途コンパイルし、コンパイル済みの t1.obj を t1.obj_inline に変更します。
(3) t2.cppを選択し、ctrl+f7を押して別途コンパイルし、コンパイル済みのt2.objをt2.obj_inlineに変更します。
(4) t1.obj_inline と t2.obj_inline を除くすべてのコンパイル済みファイルを削除します。
(5) a.h を void Foo( ){ } と修正し、テスト用のノンインライン関数にする。
(6) すべてのファイルをリビルドします。というプロンプトが表示されます。
リンクする...
t2.obj : error LNK2005: "void __cdecl Foo(void)" ( Foo@@YAXXZ ) t1.obj で既に定義されています。
Debug/cle.exe : 致命的なエラー LNK1169: オン
(7) よし、プロジェクトディレクトリの下のデバッグディレクトリを見ると、新しく生成されたobjファイルが表示されています。
下のマニュアルのリンク先を見てみましょう。
メニューの「プロジェクト」-「設定」-「リンク」を開き、以下のように「プロジェクトのオプション」にあるものをすべてコピーします。
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib kornel32.ab us32.lib gdi32.lib winspool.ab us 32.lib gdlg32.lib advis32.ribbon lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib / nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept
に変更します。
Link.exe kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib. lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept Debug/t1.obj Debug/t2.obj Debug/main.obj
ポーズ
Link.exe が前にあり、Debug/t1.obj Debug/t2.obj Debug/main.obj とあることに注意してください。
最後のpauseバッチコマンドは、プロジェクトディレクトリ(このディレクトリの中にデバッグディレクトリがあります)にlink.batという名前で保存してください。
実行すると、次のようになります。
t2.obj : error LNK2005: "void __cdecl Foo(void)" ( Foo@@YAXXZ ) がすでに定義されています。
n t1.obj
Debug/cle.exe : 致命的なエラー LNK1169: オン
素晴らしい!元のobjファイルをリンクすると、vcですべてを再構築したときと同じ結果が得られます。では、次に
バックアップした t1.obj_inline と t1.obj を上書きし、t2.obj_inline と t2.obj を上書きして手動でリンクすると、以下のようになります。
元のt1.obj_inlineとt2.obj_inlineには内部リンクシンボルが格納されているので、エラーは発生しません。Link.batを実行すると、確かに
驚くなかれ、リンクは成功しました。余分なexeファイルの下にあるデバッグディレクトリを見てください。これは、内側または外側のシンボルが、objのフラグ識別を持っていることを示しています!
(ヒント:上記のvcのf7buildのリンクを使用しないのは、ファイルの時間が変更され、buildで新しいobjが再生成されるからです。
そこで、手動リンクを使って、objが変更されないようにしています) [注意 bjの情報はdumpbin.exeで見ることができます] 。
4.#includeのルール。
どこに#includeファイルを置けばいいのかわからない人が多いのでしょうか?
1). コンポーネント自体の完成度を高める。
部品の完全性を保証するために、部品の cpp 実装ファイル (例: test.cpp) の最初の #include は、それ自身の対応するヘッダーファイル (例: test.h) であるべきです。
(ただし、プリコンパイルされたヘッダを使用する場合は、それが先である必要があります)。これは、そのコンポーネントのヘッダファイル (test.h) が依存しなければならない他のインタフェース (例えば a.h など) を、それ自身の対応するヘッダファイル (test.h) の前に cpp (test.cpp) 内に移動するのではなく、そのヘッダファイル (test.h) に配置します (これはこのコンポーネントファイル (b.cpp) が、ヘッダファイル (test.h) を含む他のヘッダファイルを再び含まなければならないと強制するからです (i.e.) 。b.cpp は "test.h" "a.h" を#include するのであれば、#include されなければなりません)" 。また、一般的にファイルヘッダ間の依存関係を最小にするように努めています。
2). コンポーネント間の依存関係を低減する。
1の上で、cpp内のファイルへの#includeを試してみてください。
このため、一般に他の変数の実装をヘッダーファイルで直接参照せず、この参照を実装ファイルに移動する必要があります。
例えば
// ファイル foo.h:
class CFoo{ <未定義
void Foo(){}。
};
// ファイル test.h:
#include "foo.h"
class CTest{ <未定義
CFoo* m_pFoo;
を公開します。
CTest() : m_pFoo(NULL){}.
void Test(){ if(m_pFoo){ m_pFoo->Foo();}}.
...........
};
// ファイル test.cpp:
#include "test.h"。
.....
上記の test.h のように、実際に #include "foo.h" を test.cpp ファイルに移動させることができます。なぜなら、CFoo* m_pFooはコンポーネントCTestの中だけで使いたいからです。
や、今後CTestの部分を使いたい部分は、test.hも含めて、foo.hのインターフェイスを見る必要がないので、以下のように、元のファイルに前方宣言をつけて修正します。
// ファイル foo.h:
class CFoo{ <未定義
を公開します。
void Foo(){}。
};
// ファイル test.h:
クラスのCFooです。
class CTest{ <未定義
CFoo* m_pFoo;
を公開します。
CTest()です。
void Test()を使用します。
//........
};
// ファイル test.cpp:
#include "test.h" // ここでは、コンポーネント自体の最初のインターフェイスヘッダーファイル
#include "foo.h" // この部分は foo.h を使用しています。
CTest::CTest() : m_pFoo(0){... <未定義
m_pFoo = 新しいCFooです。
}
void CTest::Test(){。 <未定義
if(m_pFoo){ <未定義
m_pFoo->Foo()です。
}
}
//.....
// main.cppをtestに追加します。
#include "test.h" // ここで、#include "foo.h" を見る必要はありません。
CTestのテストです。
void main(){ <未定義
test.Test()です。
}
3). ガードポストの二重包含 :
ファイルヘッダに他のヘッダをインクルードする場合(例:#include "xx.h")、includeガードポストもインクルードすることが推奨されます。
// test.hファイルの内容。
#ifndef __XX1_H_
#include "xx1.h"
#endif
#ifndef __XX2_H_
#include "xx2.h"
#endif
......
すでにxx.hファイルの冒頭に追加していますが、コンパイラは#includeファイルも開くので
は時間がかかるので、インクルードガードポストを外部に追加することで、非常に大きなプロジェクトではよりコンパイル時間を短縮することができます。
関連
-
[解決済み】cudamalloc()の使用。) なぜダブルポインタなのか?
-
[解決済み】MB/sとMiB/sを計算する方法は?
-
[解決済み】CodeBlocksで "No such file or directory "というエラーが発生する。
-
[解決済み] srand(time(NULL)) はシード値を十分に速く変更しない [duplicate] 。
-
[解決済み] GCCクロスコンパイラ使用時のprintfへの未定義参照について
-
[解決済み] 10進数から2進数への変換を行うC言語プログラムにおいて、16383以上の値を入力すると、動作しません。なぜですか?
-
[解決済み] 接続(関数)エラーでオペレーションが現在進行中エラー
-
[解決済み] strstr()関数のように、大文字小文字を無視する関数
-
[解決済み] C言語でのargv[]の長さの求め方
-
[解決済み] C言語でのソケット以外のエラー時のソケット操作
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] C: エラー: ';'トークンの前に ')' があると予想される
-
[解決済み] 未定義の `std::ios_base::Init::Init()' への参照
-
[解決済み] 3色の三角形
-
[解決済み] rdynamic` は具体的に何をするもので、どのような場合に必要なのでしょうか?
-
[解決済み] stdinとSTDIN_FILENOの違いは何ですか?
-
[解決済み] 接続拒否エラーの原因は何ですか?
-
[解決済み] OpenMP:「libgomp: スレッドの作成に失敗しました。一般ユーザーでコードを実行すると、「Resource temporarily unavailable」。
-
[解決済み] 警告: 配列の添え字は char 型です
-
[解決済み] 基本的なC言語プログラムに関する2つの質問
-
[解決済み] sin` への未定義の参照 [重複].