49

タイトルが述べたように、私の質問は明らかであり、シナリオを詳細に説明します。ファイル singleton.h には、次のように singleton パターンによって実装された singleton という名前のクラスがあります。

/*
 * singleton.h
 *
 *  Created on: 2011-12-24
 *      Author: bourneli
 */

#ifndef SINGLETON_H_
#define SINGLETON_H_

class singleton
{
private:
    singleton() {num = -1;}
    static singleton* pInstance;
public:
    static singleton& instance()
    {
        if (NULL == pInstance)
        {
            pInstance = new singleton();
        }
        return *pInstance;
    }
public:
    int num;
};

singleton* singleton::pInstance = NULL;

#endif /* SINGLETON_H_ */

次に、次のように hello.cpp というプラグインがあります。

#include <iostream>
#include "singleton.h"

extern "C" void hello() {
    std::cout << "singleton.num in hello.so : " << singleton::instance().num << std::endl;
    ++singleton::instance().num;
    std::cout << "singleton.num in hello.so after ++ : " << singleton::instance().num << std::endl;
}

プラグインがシングルトンを呼び出し、シングルトンの属性 num を変更することがわかります。

最後に、次のようにシングルトンとプラグインを使用するメイン関数があります。

#include <iostream>
#include <dlfcn.h>
#include "singleton.h"

int main() {
    using std::cout;
    using std::cerr;
    using std::endl;

    singleton::instance().num = 100; // call singleton
    cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton

    // open the library
    void* handle = dlopen("./hello.so", RTLD_LAZY);

    if (!handle) {
        cerr << "Cannot open library: " << dlerror() << '\n';
        return 1;
    }

    // load the symbol
    typedef void (*hello_t)();

    // reset errors
    dlerror();
    hello_t hello = (hello_t) dlsym(handle, "hello");
    const char *dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol 'hello': " << dlerror() << '\n';
        dlclose(handle);
        return 1;
    }

    hello(); // call plugin function hello

    cout << "singleton.num in main : " << singleton::instance().num << endl;// call singleton
    dlclose(handle);
}

メイクファイルは次のとおりです。

example1: main.cpp hello.so
    $(CXX) $(CXXFLAGS)  -o example1 main.cpp -ldl

hello.so: hello.cpp
    $(CXX) $(CXXFLAGS)  -shared -o hello.so hello.cpp

clean:
    rm -f example1 hello.so

.PHONY: clean

それで、出力は何ですか?以下があると思いました。

singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101

ただし、実際の出力は次のとおりです。

singleton.num in main : 100
singleton.num in hello.so : -1
singleton.num in hello.so after ++ : 0
singleton.num in main : 100

シングルトン クラスのインスタンスが 2 つあることが証明されます。

なんで?

4

4 に答える 4

70

まず、共有ライブラリを構築するときは、通常-fPICフラグを使用する必要があります。

これを使用しない場合、32 ビット Linux では「動作」しますが、64 ビット Linux では次のようなエラーで失敗します。

/usr/bin/ld: /tmp/ccUUrz9c.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC

-rdynamic次に、メインの実行可能ファイルのリンク行に追加すると、プログラムは期待どおりに動作します。

singleton.num in main : 100
singleton.num in hello.so : 100
singleton.num in hello.so after ++ : 101
singleton.num in main : 101

が必要な理由を理解するには、ダイナミック リンカーがシンボルを解決する方法と、ダイナミックシンボル テーブル-rdynamicについて知る必要があります。

まず、 のダイナミック シンボル テーブルを見てみましょうhello.so

$ nm -C -D hello.so | grep singleton
0000000000000b8c W singleton::instance()
0000000000201068 B singleton::pInstance
0000000000000b78 W singleton::singleton()

singleton::pInstanceこれは、2 つの弱い関数定義と、動的リンカーから見える1 つのグローバル変数があることを示しています。

次に、オリジナルの静的および動的シンボル テーブルを見てみましょうexample1( なしでリンクされています-rdynamic)。

$ nm -C  example1 | grep singleton
0000000000400d0f t global constructors keyed to singleton::pInstance
0000000000400d38 W singleton::instance()
00000000006022e0 B singleton::pInstance
0000000000400d24 W singleton::singleton()

$ nm -C -D example1 | grep singleton
$ 

そうです:singleton::pInstanceはグローバル変数として実行可能ファイルに存在しますが、そのシンボルは動的シンボル テーブルには存在しないため、動的リンカーには「見えません」。

example1ダイナミック リンカは の定義が既に含まれていることを「認識していない」ため、singleton::pInstanceその変数をhello.so既存の定義にバインドしません (これが本当に必要なことです)。

-rdynamicリンク行に追加すると、次のようになります。

$ nm -C  example1-rdynamic | grep singleton
0000000000400fdf t global constructors keyed to singleton::pInstance
0000000000401008 W singleton::instance()
00000000006022e0 B singleton::pInstance
0000000000400ff4 W singleton::singleton()

$ nm -C -D  example1-rdynamic | grep singleton
0000000000401008 W singleton::instance()
00000000006022e0 B singleton::pInstance
0000000000400ff4 W singleton::singleton()

singleton::pInstanceこれで、メインの実行可能ファイル内のの定義がダイナミック リンカから見えるようになり、ロード時にその定義を「再利用」しますhello.so

LD_DEBUG=bindings ./example1-rdynamic |& grep pInstance
     31972: binding file ./hello.so [0] to ./example1-rdynamic [0]: normal symbol `_ZN9singleton9pInstanceE'
于 2011-12-24T21:16:11.690 に答える
5

ランタイムロードの共有ライブラリを使用する場合は注意が必要です。このような構造は厳密には C++ 標準の一部ではないため、そのような手続きのセマンティクスがどうなるかを慎重に検討する必要があります。

まず、何が起こっているかというと、共有ライブラリが独自の個別のグローバル変数を認識しているということですsingleton::pInstance。何故ですか?実行時に読み込まれるライブラリは、基本的に、たまたまエントリ ポイントを持たない別個の独立したプログラムです。しかし、他のすべては実際には別のプログラムのようであり、動的ローダーはそれをそのように扱います。たとえば、グローバル変数を初期化するなどです。

動的ローダーは、静的ローダーとは関係のない実行時機能です。静的ローダーは C++ 標準実装の一部であり、メイン プログラムが開始するにメイン プログラムのすべてのシンボルを解決します。一方、ダイナミックローダーは、メインプログラムがすでに開始された後にのみ実行されます。特に、メイン プログラムのすべてのシンボルは既に解決されている必要があります。メイン プログラムのシンボルを動的に自動的に置き換える方法はまったくありません。ネイティブ プログラムは、体系的な再リンクを可能にする方法で「管理」されていません。(おそらく何かがハッキングされる可能性がありますが、体系的で移植可能な方法ではありません。)

したがって、実際の問題は、試みている設計上の問題をどのように解決するかです。ここでの解決策は、すべてのグローバル変数へのハンドルをプラグイン関数に渡すことです。メイン プログラムにグローバル変数の元の (そして唯一の) コピーを定義させ、それへのポインタを使用してライブラリを初期化します。

たとえば、共有ライブラリは次のようになります。まず、シングルトン クラスにポインター ツー ポインターを追加します。

class singleton
{
    static singleton * pInstance;
public:
    static singleton ** ppinstance;
    // ...
};

singleton ** singleton::ppInstance(&singleton::pInstance);

どこでも*ppInstance代わりに使用するようになりました。pInstance

プラグインで、メイン プログラムからのポインターへのシングルトンを構成します。

void init(singleton ** p)
{
    singleton::ppInsance = p;
}

メイン関数は、プラグインの初期化を呼び出します。

init_fn init;
hello_fn hello;
*reinterpret_cast<void**>(&init) = dlsym(lib, "init");
*reinterpret_cast<void**>(&hello) = dlsym(lib, "hello");

init(singleton::ppInstance);
hello();

これで、プラグインはシングルトン インスタンスへの同じポインターをプログラムの残りの部分と共有します。

于 2011-12-24T11:35:00.290 に答える
3

簡単な答えはここにあると思います: http ://www.yolinux.com/tokyoS/LibraryArchives-StaticAndDynamic.html

静的変数がある場合、それはオブジェクト(.o、.aおよび/または.so)に格納されます

実行される最終オブジェクトにオブジェクトの2つのバージョンが含まれている場合、たとえば、シングルトンオブジェクトのデストラクタを呼び出すなど、予期しない動作が発生します。

メインファイルで静的メンバーを宣言したり、-rdynamic / fpicを使用したり、 ""コンパイラ指令を使用したりするなど、適切な設計を使用すると、トリックの部分が実行されます。

makefileステートメントの例:

$ g++ -rdynamic -o appexe $(OBJ) $(LINKFLAGS) -Wl,--whole-archive -L./Singleton/ -lsingleton -Wl,--no-whole-archive $(LIBS) 

これがうまくいくことを願っています!

于 2012-07-26T19:19:50.580 に答える