11

次の 2 つのファイルがあるとします。

/**
 * class.cpp
 */ 
#include <stdio.h>
class foo 
{
private:
        int func();
};

int foo::func(void)
{
        printf("[%s:%d]: %s\n", __FILE__, __LINE__, __FUNCTION__);
        return -1; 
}

/**
 * main.cpp
 */ 
#include <stdio.h>
namespace foo 
{
        int func(void);
}
int main(void)
{
        int ret = foo::func();
        printf("[%s:%d]: ret=%d\n", __FILE__, __LINE__, ret);
        return 0;
}

次のようにコンパイルされます。

g++ -o a.out main.cpp class.cpp 

実行可能ファイルからの出力があります。

[class.cpp:15]: func
[main.cpp:14]: ret=-1

そして最後に私の質問:

このサンプル コードがエラーなしでコンパイルされ、クラス fooのプライベートメソッドを呼び出すことができるのはなぜですか?

gcc 4.6.3 でコンパイルされていますが、それだけではありません。私は、コンパイラがこれら 2 つのシンボル (名前空間fooのfunc関数とクラス fooのプライベート関数foo )を区別しないことを知っています。nmからの出力:

nm class.o
00000000 T _ZN3foo4funcEv
00000017 r _ZZN3foo4funcEvE12__FUNCTION__
         U printf

nm main.o
         U _ZN3foo4funcEv
00000000 T main
         U printf

この振る舞いが正しいかどうかお聞きしたいです。IMHOこれは正しい動作ではなく、まったく安全ではありません(カプセル化を壊します)。

Visual Studio 2008 のコンパイラは、これら 2 つのシンボルをリンクしていません。

4

2 に答える 2

3

コンパイラが文句を言わないのはなぜですか?

コンパイラに関する限り、「クラス」、「構造体」、および「名前空間」はすべて名前空間を定義することに注意してください。したがって、コンパイラはそれに応じてシンボルを装飾します。クラスと名前空間の両方を同じファイルで定義すると問題が発生しますが、ここではそうではありません。

リンカーが文句を言わないのはなぜですか?

あなたがコードを書いた方法では、func()定義された innamespace fooよりも弱いin がfunc()定義されたままになりclass fooます。基本的に、func()定義された innamespace fooは実装のない単なる署名です。実装が にないため、実行時にシンボルを解決するのはリンカーに任されていることがわかりますmain.cpp

nm main.o
       U _ZN3foo4funcEv
//Here^^^^

このように、名前空間とクラス名がたまたま同じであったため (結果として のシンボルが同じになるfoo::func)、リンカはリンク時にシンボルを解決し、同じシンボルで強力な定義を見つけ、それに対してリンクします。

同様func()にを実装する場合:namespace foo

/**
 * main.cpp
 */
#include <stdio.h>

namespace foo
{
    int func(void) {
        printf("NM_FOO [%s:%d]: %s\n", __FILE__, __LINE__, __FUNCTION__);
        return -1;
    };
}
int main(void)
{
    int ret = foo::func();
    printf("[%s:%d]: ret=%d\n", __FILE__, __LINE__, ret);
    return 0;
}

リンカーが次のように不平を言っていることがわかります。

duplicate symbol foo::func()     in:
/var/folders/.../class.o
/var/folders/.../main.o
ld: 1 duplicate symbol for architecture x86_64

今度は main.o を見ると、次のように表示されます。

0000000000000064 T __ZN3foo4funcEv
0000000000000158 S __ZN3foo4funcEv.eh
00000000000000e0 s __ZZN3foo4funcEvE12__FUNCTION__
0000000000000000 T _main
0000000000000128 S _main.eh
                 U _printf

そしてclass.o:

0000000000000000 T __ZN3foo4funcEv
00000000000000a0 S __ZN3foo4funcEv.eh
0000000000000080 s __ZZN3foo4funcEvE12__FUNCTION__
                 U _printf

どちらも同じ関数シンボルを同等に強力に定義しているため、リンカー エラーが発生します。

リンカーは名前空間とクラスの違いを認識していないことに注意してください。オブジェクト コード内のシンボルを解決します。強い再定義が発生した場合にのみ文句を言います。リンカーの世界では、1 つの強力な定義を持つ 1 つまたは複数の弱い定義はまったく問題ありません。

于 2013-03-20T19:52:39.233 に答える
2

foo()main.cpp の名前空間のメンバーとして定義したため、コンパイラはそれを処理しました。クラス/構造体/名前空間のパブリック/プライベートなどの違いは、関数の定義を知っているコンパイラに依存します-ここでは、意図的にそれをだますように設定しました。

リンカーはそのような違いを認識せず、単にシンボル名を解決します。コンパイラの場合、関数名の装飾は同じようになります。シンボル名の装飾方法は C++ では規定されていないため、これは完全に有効な動作です。

于 2013-03-20T19:33:16.500 に答える