1

前言:

这个问题与这些问题密切相关:...
- C++:同时避免静态初始化顺序问题和竞争条件
-如何检测内存块的分配位置?
...但他们没有积极的解决方案,我的实际目标用例略有不同。

在对象的构造过程中,我需要知道它是在静态内存块(BSS)中初始化还是在堆中实例化。

原因如下:

  • 对象本身被设计为在构造函数中初始化为“全零”-因此,如果对象被静态初始化,则不需要初始化-加载程序时,具有所有对象的整个块已经设置为零。

  • 对象的静态实例可以被其他静态分配的对象使用并改变对象的一些成员变量

  • 静态变量的初始化顺序 不是预先确定的——即我的目标对象可以在其构造函数被调用之前被调用,从而改变它的一些数据,并且构造函数可以稍后根据一些未知的静态初始化顺序被调用,从而已经清除更改数据。这就是为什么我想在构造函数中为静态分配的对象禁用代码。

  • 注意:在某些情况下,对象是严重多线程访问的主题(它具有一些 InterlockedIncrement/Decrement 逻辑),并且必须在任何线程可以接触它之前完全初始化它 - 如果我在 Heep 中显式分配它,我可以保证,但不在静态区域(但我也需要静态对象)。

用于说明案例的示例代码:

struct MyObject
{
    long counter;

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

目前我正在尝试检查“this”指针是否在某个预定义的范围内,但这并不可靠。

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 将被认为是使用建议的方法静态分配的。

编辑:一个更干净但更复杂的解决方案可能是对新的进一步定制,您可以在其中跟踪动态分配的内存块。

第二次编辑:如果您只是想禁止静态分配,为什么不将构造函数设为私有并在类中添加工厂函数动态创建对象并传递指针?

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

我认为你控制它的最好方法是为你的班级创建一个工厂。这样您就可以完全控制对象的创建方式,而不是对使用的内存进行复杂的猜测。

于 2012-10-26T07:02:29.003 回答
0

第一个答案是:不可移植,在某些平台上可能根本不可能。在 Solaris 下(我认为 Linux 也是如此),有一个隐式定义的全局符号end,任意地址的比较有效,如果this < &end(在适当的转换之后),变量是静态的,至少只要不涉及动态加载。但这远非普遍。(无论平台如何,只要涉及动态链接,它肯定会失败。)

我过去使用的解决方案是手动进行区分。基本上,我设计了这个类,使普通的构造函数做与零初始化相同的事情,然后我提供了一个特殊的无操作构造函数用于静态对象:

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

在定义具有静态生命周期的实例时,您使用第二个构造函数:

MayBeStatic object( MayBeStatic::isStatic );

我不认为这是由标准保证的。我认为允许实现在调用构造函数之前以任何方式修改内存,特别是,我认为允许在调用构造函数之前立即“重做”零初始化。但是,没有人这样做,因此您在实践中可能是安全的。

或者,您可以将所有静态实例包装在一个函数中,以便它们是局部静态的,并且将在第一次调用该函数时被初始化:

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

当然,每个静态实例都需要一个单独的函数。

于 2012-10-26T07:40:29.533 回答
0

看起来经过一段时间的思考,我找到了一个可行的解决方案来识别块是否在静态区域中。如果有潜在的陷阱,请告诉我。

专为 MS Windows 设计,这是我的目标平台 - 另一个操作系统实际上是指另一个版本的 MS Windows:XP -> Win7。这个想法是获取已加载模块(.exe 或 .dll)的地址空间并检查块是否在此地址空间内。计算静态区域的开始/结束的代码被放入'lib'段,因此它应该在'user'段的所有其他静态对象之前执行,即构造函数可以假设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 回答