确定是否将对象分配在静态内存块中(还是如何避免数据竞赛条件)

Identify if object is allocated in static memory block (or how to avoid data race conditions)

本文关键字:何避免 数据 条件 竞赛 是否 对象 分配 内存 静态      更新时间:2023-10-16

序言:

这个问题与这些问题密切相关:...
-C :避免静态初始化订单问题和种族条件同时
- 如何检测位置分配的位置?
...但是它们没有积极的解决方案,我的实际目标用例略有不同。

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

以下原因:

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

  • 的静态实例该对象可以由其他静态分配的对象使用,并更改对象的某些成员变量

  • 静态变量初始化的初始化顺序没有预先确定 - 即可以在调用其构造函数之前调用我的目标对象,从而更改其某些数据,并根据某些未知的构建器来调用构造函数,并根据某些未知的顺序调用。静态的初始化因此清除已经更改的数据。这就是为什么我想在构造函数中禁用静态分配对象的代码。

  • 注意:在某些情况下,对象是严重的多线程访问的主题(它具有一定的互锁/减少逻辑),并且必须在任何线程触摸它之前完全初始初始初始化 - 如果我可以保证的是我明确地将其分配在远处,但不在静态区域中(但我也需要静态对象)。

示例代码以说明案例:

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));

更新:示例用例不适用新的新操作员。代码是"伪代码",只是为了说明用例。

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); 

您可以通过覆盖类的new操作员来实现这一目标。在定制的new中,您可以在分配的内存中设置"魔术字节",您可以在以后检查一下。这将不允许将堆栈与堆区分开,而是静态地与动态分配的对象区分,这可能足够。但是请注意,在以下情况下

class A {
};
class B {
   A a;
};
//...
B* b = new B;

b.a将被视为用建议的方法静态分配。

编辑:一个更清洁但更复杂的解决方案可能是新的自定义,您可以在其中跟踪动态分配的内存块。

第二次编辑:如果您只想禁止静态分配,为什么不只是将构造函数私有化并在类中添加出厂功能,然后动态创建对象并提供指针?

class A {
private:
    A () { ... }
public:
    static A* Create () { return new A; }
};

我认为,控制此目的的最佳方法是为您的班级创建工厂。这样,您可以完全控制如何创建对象,而不是对使用的内存进行复杂的猜测。

第一个答案是:不正常,而且可能根本不可能一些平台。在Solaris的领导下(我也认为Linux),有一个隐式定义的全局符号end,任意比较地址有效,如果是this < &end(适当后转换),该变量是静态的,至少只有动态涉及加载。但这远非一般。(绝对无论平台如何,任何时间都涉及动态链接。)

我过去使用的解决方案是手动进行区分。基本上,我设计了课程,以便普通构造函数完成与零初始化相同,然后我提供了一个特殊的no-op用于静态对象的构造函数:

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;
}

当然,您需要为每个静态实例一个单独的功能。

看起来像是在思考一段时间后,我找到了一个可行的解决方案,可以识别块是否在静态区域。请让我知道,如果有潜在的陷阱。

是为MS Windows设计的,这是我的目标平台 - 由另一个操作系统,我实际上是另一个版本的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;
}