2016-06-20 11 views
2

次のヘッダーfoo.hという仮定でき:なぜリンカーはカプセル化を保持しませんか?

class Foo { 
    private: 
    void print() const; 
}; 

およびfoo.cpp次のメソッド印刷が宣言されていない限り

#include <iostream> 

#include "foo.h" 

void Foo::print() const { 
    std::cout << "Secret" << std::endl; 
} 

別のヘッダfoo1.h、すなわちfoo.hという同じです公共:

class Foo { 
    public: 
    void print() const; 
}; 

、これはちょうどfoo1.hで印刷を呼び出すことmain.cppに、次のようになります。

#include "foo1.h" 

int main() { 
    Foo f; 
    f.print(); 
    return 0; 
} 

何私のために奇妙なようであることは、以下のリンクつもり作業その:

g++ foo.cpp -c -o foo.o 
g++ main.cpp -c -o main.o 
g++ main.o foo.o -o exec 
./exec 

最後のコマンドの出力は以下となります。Fooクラスの具体的な実装を知っているけど、知らなくてもそう

Secret 

その宣言とそのオブジェクトファイルを持っているので、プライベート宣言されているにもかかわらずメソッドを使用できる状況を作り出すことができます。

私の質問は以下のとおりです。

  1. なぜそれが動作しますか?リンカーは私的宣言と公的宣言を考慮しない?

  2. この動作は実際には有効ですか?はいの場合、どのように使用されますか?私はそれがテストのために有用かもしれないと思います。

+0

私はそれが未定義の動作だと思う。 C++言語の弁護士は確かにそれを証明することができますが、私はそれを公にして宣言して私的メンバー関数にアクセスしようとしているのは、未定義の振る舞いとして仕様に入れたものだと思います。しかし、私はC + +の専門家ではないので、評判の良い情報源からの回答を待つことが最善の方法です。 – dreamlax

答えて

4

最初に、「1つの定義ルール」(C++ 11 3.2/5「1つの定義ルール」に違反しているので、異なる翻訳単位の別々のクラス定義は「同じトークンシーケンスから成っている」 )、ツールチェーンに関しては何かがあります。エラーを診断したり、(テストのように)動作するように見えるプログラムを作成することができます。

実験結果には、コンパイラによってクラスメンバーへのアクセスが強制され、メンバーFoo::print()へのアクセスがpublicであることがコンパイラに伝えられているという単純な理由があります。

他の理由(オーバーロードなど)のために実行される名前のmangleのメンバーへのアクセスを、ツールチェーンがエンコードするようにしています。しかし、標準ではツールチェーンがそれを強制する必要はないため、実装者はリンク時にアクセス制御を考慮する必要がないと判断したようです。言い換えれば、リンカーが使用する外部シンボルにアクセス制御をエンコードすることは実現可能だと思いますが、その作業は行われませんでした。おそらく厳密には話す必要はないからです。マイクロソフトC++を使用すると、リンク時にエラーが出るんので、外部名にメンバーへのアクセスを取り入れないこと

注:ここでは

testmain.obj : error LNK2019: unresolved external symbol "public: void __thiscall Foo::print(void)const " ([email protected]@@QBEXXZ) referenced in function _main testmain.exe : fatal error LNK1120: 1 unresolved externals 

は(c++filtデコードと一緒に)生成++シンボルのグラム以下のとおりです。

D:\so-test>nm test.o | grep Foo 
000000000000008c t _GLOBAL__sub_I__ZNK3Foo5printEv 
0000000000000000 T _ZNK3Foo5printEv 

D:\so-test>nm testmain.o | grep Foo 
       U _ZNK3Foo5printEv 

D:\so-test>c++filt _ZNK3Foo5printEv 
Foo::print() const 

そしてここMS C++は(デコードと一緒に)を生成するシンボルである:

D:\so-test>dumpbin /symbols test.obj | grep Foo 
22D 00000000 SECTBA notype() External  | [email protected]@@ABEXXZ (private: void __thiscall Foo::print(void)const) 

D:\so-test>dumpbin /symbols testmain.obj | grep Foo 
009 00000000 UNDEF notype() External  | [email protected]@@QBEXXZ (public: void __thiscall Foo::print(void)const) 
+0

興味深いことに、MicrosoftはGCCよりも標準に準拠しています。 –

+3

どちらの動作も標準に準拠しています。私は、あなたがこの奇妙な問題に遭遇した場合、Microsoftの方がより役に立ちそうだと思います。 –

1

リンカーはシンボルのみを解決します。各C++ファイルは独立してコンパイルされ、宣言が#include文を介して持ち込まれ、(.oファイル内に)シンボルが構築され、リンカはコンパイラの出力で動作します。 private、protected、publicなどのC++アクセス修飾子は、コンパイラのみに影響し、リンカーには影響しません。 (技術的には、リンカーはクラスについても気づいていませんが、クラスメンバーの装飾記号/記号化されたシンボル名のみを扱います)。クラスの宣言を変更してメンバーのアクセスを変更したり、それらを非表示にします。

3

規格、セクション[basic.def.odr]

  1. There can be more than one definition of a class type [snip] in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

    — each definition of D shall consist of the same sequence of tokens; and

クラスの2つの定義は、トークンの同じ配列からなるありませんので、あなたのプログラムは、このルールに違反します。 1つの定義ルールに違反すると、プログラムが不正な形になります。

  1. Why does it work?

標準が指定されていないため、この状況をどのように処理する必要がありますか。ツールチェーンは自由にプログラムのリンクを拒否できますが、リンクは正常に実行できます。あなたのリンカーは後者をします。別のリンカーが前者を行うかもしれません。

Linker doesn't consider private and public declarations?

実験で確認したように、リンカーはアクセス指定子を考慮していないようです。リンカーはそれらを考慮する必要はありません。それらは純粋にコンパイル時の概念です。

Is this behavior useful in practice? If yes, how is it used?

これに依存しているのはevilです。つまり、ソースが再コンパイルに利用できないときに、APIの制限を回避するための不正なハックとして、野生で時折見られることがあります。

+0

私はちょっとだけ気をつけます。どちらの宣言も、アクセス修飾子が異なるだけで、トークンの順序が同じです。また、ヘッダーのバージョンが1つしかない場合でも、ヘッダーをインクルードする前に、ソースファイルの1つに '#define private public'のような何かをすることはできません。 –

+0

例:http://stackoverflow.com/questions/729363/is-it-possible-to-access-private-members-of-a-class –

+2

@DanKorn [keywords](http://eel.is/ C++ draft/lex.key)アクセス指定子のようにトークンはありますか?そうではありませんか?また、前処理トークンは(前処理ではない)[トークン](http://eel.is/c++draft/lex.pptoken)に変換されるので、その定義はルールを曲げるのに役立ちません。ルールに従わなければならない。 – user2079303

関連する問題