1

序文:

この質問はこれらのものと密接に関連しています: ...
- C++: 静的な初期化順序の問題と競合状態を同時に回避
する -メモリのブロックが割り当てられた場所を検出する方法は?
...しかし、彼らには前向きな解決策がなく、実際のターゲットのユースケースはわずかに異なります。

オブジェクトの構築中に、オブジェクトが静的メモリ ブロック ( BSS ) で初期化されているか、ヒープでインスタンス化されているかを知る必要があります。

理由は次のとおりです。

  • オブジェクト自体は、コンストラクターで「すべてゼロ」に初期化されるように設計されています。したがって、オブジェクトが静的に初期化されている場合、初期化は必要ありません。プログラムがロードされたときに、すべてのオブジェクトを含むブロック全体が既にゼロに設定されています。

  • オブジェクトの静的インスタンスは、静的に割り当てられた他のオブジェクトで使用でき、オブジェクトの一部のメンバー変数を変更できます

  • 静的変数の初期化の順序は 事前に決定されていません-つまり、コンストラクターが呼び出される前にターゲットオブジェクトを呼び出すことができるため、そのデータの一部が変更され、静的変数の初期化の未知の順序に従ってコンストラクターが後で呼び出される可能性があるため、既にクリアされています変更されたデータ。そのため、静的に割り当てられたオブジェクトのコンストラクターでコードを無効にしたいと考えています。

  • 注:いくつかのシナリオでは、オブジェクトは深刻なマルチスレッドアクセスの対象であり(いくつかの InterlockedIncrement/Decrement ロジックがあります)、スレッドがそれに触れる前に完全に初期化する必要があります-ヒープに明示的に割り当てた場合に保証できること、しかし静的領域ではありません(ただし、静的オブジェクトにも必要です)。

ケースを説明するためのコードのサンプル:

struct MyObject
{
    long counter;

    MyObject() {
        if( !isStaticallyAllocated() ) {
            counter = 0;
        }
    }
    void startSomething() { InterlockedIncrement(&counter); }
    void endSomething() { InterlockedDecrement(&counter); }
};

現時点では、「この」ポインターが事前定義された範囲内にあるかどうかを確認しようとしていますが、これは確実に機能しません。

LONG_PTR STATIC_START = 0x00400000;
LONG_PTR STATIC_END   = 0x02000000;
bool isStatic = (((LONG_PTR)this >= STATIC_START) && (LONG_PTR)this < STATIC_END));

更新: 明示的な new 演算子が適用されないサンプル ユース ケース。コードは「疑似コード」であり、ユースケースを説明するためのものです。

struct SyncObject() {
    long counter;
    SyncObject() { 
        if( !isStaticallyAllocated() ) {
            counter = 0;
        } }
    void enter() { while( counter > 0 ) sleep(); counter++; }
    void leave() { counter--; }
}

template <class TEnum>
struct ConstWrapper {
    SyncObject syncObj;
    TEnum m_value;

    operator TEnum() const { return m_value; }
    LPCTSTR getName() {
        syncObj.enter();
        if( !initialized ) {
            loadNames();
            intialized = true;
        }
        syncObj.leave();
        return names[m_value];
    }
}

ConstWrapper<MyEnum> MyEnumValue1(MyEnum::Value1); 
4

4 に答える 4

1

newクラスの演算子を上書きすることで、おそらくこれを実現できます。カスタマイズnewした では、割り当てられたメモリ内に「マジック バイト」を設定できます。これは後で確認できます。これにより、スタックをヒープから区別することはできませんが、動的に割り当てられたオブジェクトから静的に区別することができます。これで十分な場合があります。ただし、次の場合は注意してください。

class A {
};

class B {
   A a;
};

//...

B* b = new B;

ba は、提案された方法で静的に割り当てられたと見なされます。

編集:よりクリーンですが、より複雑なソリューションは、動的に割り当てられたメモリ ブロックを追跡できる new をさらにカスタマイズすることです。

2番目の編集:静的割り当てを禁止したいだけなら、コンストラクターをプライベートにして、オブジェクトを動的に作成してポインターを配信するクラスにファクトリー関数を追加してみませんか?

class A {
private:
    A () { ... }
public:
    static A* Create () { return new A; }
};
于 2012-10-26T06:47:24.577 に答える
0

しばらく考えた後、ブロックが静的領域にあるかどうかを識別するための実行可能な解決策を見つけたようです。潜在的な落とし穴がある場合は、お知らせください。

私のターゲット プラットフォームである MS Windows 用に設計されています。別の OS とは、実際には別のバージョンの MS Windows を意味していました: XP -> Win7。アイデアは、ロードされたモジュール (.exe または .dll) のアドレス空間を取得し、ブロックがこのアドレス空間内にあるかどうかを確認することです。静的領域の開始/終了を計算するコードは「lib」セグメントに配置されるため、「ユーザー」セグメントからの他のすべての静的オブジェクトの前に実行する必要があります。つまり、コンストラクターは staticStart/End 変数が既に初期化されていると見なすことができます。

#include <psapi.h>

#pragma warning(push)
#pragma warning(disable: 4073)
#pragma init_seg(compiler)
#pragma warning(pop)

HANDLE gDllHandle = (HANDLE)-1;
LONG_PTR staticStart = 0;
LONG_PTR staticEnd = 0;

struct StaticAreaLocator {
    StaticAreaLocator() {
        if( gDllHandle == (HANDLE)-1 )
            gDllHandle = GetModuleHandle(NULL);

        MODULEINFO  mi;
        GetModuleInformation(GetCurrentProcess(), (HMODULE)gDllHandle, &mi, sizeof(mi));

        staticStart = (LONG_PTR)mi.lpBaseOfDll;
        staticEnd = (LONG_PTR)mi.lpBaseOfDll + mi.SizeOfImage;

        // ASSERT will fail in DLL code if gDllHandle not initialized properly
        LONG_PTR current_address;
        #if _WIN64
            ASSERT(FALSE) // to be adopted later
        #else
            __asm {
                        call _here
                _here:  pop eax                     ; eax now holds the [EIP]
                        mov [current_address], eax
            }
        #endif
        ASSERT((staticStart <= current_address) && (current_address < staticEnd));
        atexit(cleanup);
    }

    static void cleanup();
};

StaticAreaLocator* staticAreaLocator = new StaticAreaLocator();

void StaticAreaLocator::cleanup() {
    delete staticAreaLocator;
    staticAreaLocator = NULL;
}
于 2012-10-27T10:13:48.560 に答える
0

最初の答えは、移植性がなく、一部のプラットフォームではまったく不可能な場合があるということです。Solaris (および Linux も同様だと思います) では、暗黙的に定義されたグローバル シンボル がありend、任意のアドレスの比較が機能し、this < &end(適切な変換後) 変数が静的である場合、少なくとも動的な読み込みが含まれていない限りは. しかし、これは一般的なものではありません。(そして、プラットフォームに関係なく、動的リンクが関係するときはいつでも確実に失敗します。)

私が過去に使用した解決策は、手動で区別することでした。基本的に、通常のコンストラクターがゼロ初期化と同じことを行うようにクラスを設計し、静的オブジェクトで使用する特別なノーオペレーション コンストラクターを提供しました。

class MayBeStatic
{
public:
    enum ForStatic { isStatic };
    MayBeStatic() { /* equivalent of zero initialization */ };
    MayBeStatic( ForStatic ) { /* do absolutely nothing! */ };
    //  ...
};

静的な有効期間を持つインスタンスを定義するときは、2 番目のコンストラクターを使用します。

MayBeStatic object( MayBeStatic::isStatic );

これが標準で保証されているとは思いません。実装では、コンストラクターを呼び出す前に任意の方法でメモリを変更することが許可されていると思います。特に、コンストラクターを呼び出す直前にゼロの初期化を「やり直す」ことが許可されていると思います。ただし、どれもそうではないので、実際にはおそらく安全です。

または、すべての静的インスタンスを関数でラップして、それらがローカル静的であるようにし、関数が最初に呼び出されたときに初期化されるようにすることもできます。

MayBeStatic&
getStaticInstance()
{
    static MayBeStatic theInstance;
    return theInstance;
}

もちろん、静的インスタンスごとに個別の関数が必要になります。

于 2012-10-26T07:40:29.533 に答える
0

これを制御する最善の方法は、クラスのファクトリを作成することだと思います。そうすれば、どのメモリが使用されているかを複雑に推測する代わりに、オブジェクトの作成方法を完全に制御できます。

于 2012-10-26T07:02:29.003 に答える