4

そのため、誰かが、エラーLNK2005:オブジェクトで既に定義されているシンボル(Visual Studio 2010を使用)でリンクに失敗したプロジェクトを持って来ました。この場合、私はが間違っているかを知っています(したがって、正しい解決策を示すことができます)が、それについての良い説明を与えるために(再発を防ぐために)レベルでこれが間違っている理由はわかりません。

// something.h
#ifndef _SOMETHING_H
#define _SOMETHING_H
int myCoolFunction();

int myAwesomeFunction() // Note implementing function in header
{
    return 3;
}
#endif

-

// something.cpp
#include "something.h"
int myCoolFunction()
{
    return 4;
}

-

// main.cpp
#include <iostream>
#include "something.h"

int main()
{
    std::cout << myAwesomeFunction() << std::endl;
}

これはリンクに失敗し、myAwesomeFunction()を.cppに入れ、宣言を.hに残すことで修正されます。

リンカーがどのように機能するかについての私の理解は、ここからかなり得られます。私の理解では、私たちは一箇所で必要とされるシンボルを提供しています。

LNK2005に関するMSDNの記事を調べました。これは、リンカーの動作を期待する方法と一致します(シンボルを複数回提供する->リンカーが混乱している)が、このケースをカバーしていないようです(つまり、明らかなことを理解していないことを意味します)リンクについて)。

GoogleとStackOverflowは、#ifndefまたはを含まない人々に問題を#pragma once引き起こします(これは提供されたシンボルの複数の宣言につながります)

このサイトで見つけた関連する質問にも同じ問題がありますが、その答えは、なぜこの問題が私の理解レベルに十分に達しているのかを説明していません。

問題があり、解決策はわかっていますが、解決策が機能する理由がわかりません

4

3 に答える 3

3

通常のC++プロジェクトでは、各実装(または.cpp)ファイルを個別にコンパイルします。通常、ヘッダー(または.h)ファイルをコンパイラに直接渡すことはありません。すべての前処理とインクルードが実行された後、これらの各ファイルは変換ユニットになります。したがって、あなたが与えた例では、次のように見える2つの翻訳単位があります。

  • main.cpp翻訳単位:

    // Contents of <iostream> header here
    
    int myCoolFunction();
    
    int myAwesomeFunction() // Note implementing function in header
    {
        return 3;
    }
    
    int main()
    {
        std::cout << myAwesomeFunction() << std::endl;
    }
    
  • something.cpp翻訳単位:

    int myCoolFunction();
    
    int myAwesomeFunction() // Note implementing function in header
    {
        return 3;
    }
    
    int myCoolFunction()
    {
        return 4;
    }
    

両方にが含まれているため、これらの変換ユニットの両方に重複コンテンツが含まれていることに注意してくださいsomething.h。ご覧のとおり、上記の翻訳単位の1つだけに。の定義が含まれていますmyCoolFunction。それは良い!ただし、どちらにもの定義が含まれていますmyAwesomeFunction。それは良くないね!

翻訳ユニットが個別にコンパイルされた後、それらはリンクされて最終的なプログラムを形成します。翻訳単位全体での複数の宣言に関する特定の規則があります。それらのルールの1つは(§3.2/ 4)です:

すべてのプログラムには、そのプログラムでodrで使用されるすべての非インライン関数または変数の定義が1つだけ含まれている必要があります。診断は必要ありません。

プログラム全体で複数の定義があるmyAwesomeFunctionため、ルールに違反しています。そのため、コードは正しくリンクされません。

リンカの観点から考えることができます。これらの2つの変換ユニットがコンパイルされると、2つのオブジェクトファイルが作成されます。リンカの仕事は、オブジェクトファイルを接続して最終的な実行可能ファイルを形成することです。myAwesomeFunctionそのため、inの呼び出しをmain確認し、オブジェクトファイルの1つで対応する関数定義を見つけようとします。ただし、2つの定義があります。リンカはどちらを使用するかわからないため、あきらめます。

myAwesomeFunction次に、で定義した場合の変換単位がどのようになるかを見てみましょうsomething.cpp

  • 固定main.cpp翻訳単位:

    // Contents of <iostream> header here
    
    int myCoolFunction();
    
    int myAwesomeFunction();
    
    int main()
    {
        std::cout << myAwesomeFunction() << std::endl;
    }
    
  • 固定something.cpp翻訳単位:

    int myCoolFunction();
    
    int myAwesomeFunction();
    
    int myCoolFunction()
    {
        return 4;
    }
    
    int myAwesomeFunction()
    {
        return 3;
    }
    

今では完璧です。myAwesomeFunction現在、プログラム全体での定義は1つだけです。リンカは、の呼び出しを確認するmyAwesomeFunctionと、リンク先の関数定義を正確mainに認識します。

于 2013-03-13T12:24:19.053 に答える
1

リンカは、単一定義規則に違反したことを通知しているだけです。これは、C ++の基本的な、十分に文書化されたルールです。インクルードガードや#pragma onceディレクティブを使用しても解決されませんが、無料の関数の場合は、マークを付けるinlineか、実装をソースファイルに移動します。

非インラインメソッドがヘッダーに実装されている場合、そのヘッダーを含むすべての変換ユニットがそれを定義します。対応する.objファイルがリンクされている場合、リンカは同じシンボルが複数回エクスポート(および定義)されていることを検出し、文句を言います。

実装をcppファイルに移動すると、初期定義が効果的に宣言に変換されます。

于 2013-03-13T12:20:17.180 に答える
-2

myAwesomeFunctionは2つのソースファイルで定義されています:something.cppmain.cpp。その実装をソースファイルの1つに移動するか、この関数を静的として宣言します。

于 2013-03-13T12:22:08.777 に答える