2

私のサーバーモジュールでは、log4cxx ライブラリがクラッシュすることがありました。

その理由は ...

LevelPtr Level::getTrace() {
   static LevelPtr level(new Level(Level::TRACE_INT, LOG4CXX_STR("TRACE"), 7));
   return level;
}

static LevelPtr は null ptr を返します。

次のコードをテストしました。

int start_flag = 0;

class test_dummy {
public:
    int mi;
    test_dummy() : mi(1)
    { 
        std::cout << "hey!\n"; 
    }
    static test_dummy* get_p() 
    { 
        static test_dummy* _p = new test_dummy();
        return _p;
    }
};

void thread_proc()
{
    int i = 0;
    while (start_flag == 0)
    {
        i++;
    }
    if (test_dummy::get_p() == 0)
    {
        std::cout << "error!!!\n";
    }
    else
    {
        std::cout << "mi:" << test_dummy::get_p()->mi << "\n";
    }
}

void main()
{
    boost::thread *pth_array[5] = {0,};

    for (int i = 0; i < 5; i++)
    {
        pth_array[i] = new boost::thread(thread_proc);
    }
    start_flag = 1;
    for (int i = 0; i < 5; i++)
    {
        pth_array[i]->join();
    }
    std::cin.ignore();
}

本当にスレッドセーフではありませんが、 get_p() が別の割り当てられたアドレスではなく null ポインターを返す理由に興味があります。

new() 操作中に値が 0 に設定されたためですか?

4

2 に答える 2

0

コンパイラが提供するこのコードには、競合状態があります。

if (!level_initialized)
{
    level_initialized = 1;
    level = new Level(...);
}
return level; 

(正確にはそのようには見えません。より複雑ですが、一般的な考え方は理解できると思います)

clang++ 3.5 では、この種の競合を防ぐためのロックがあるように見えますが、コンパイラによって生成されたコードを実際に見なければ、何が起こっているのかを正確に言うことは不可能です。しかし、私はこれが起こることだと思います。

これがclang ++ 3.5が生成するものです(混乱を除く)

_Z8getTracev:                           # @_Z8getTracev
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp
    cmpb    $0, _ZGVZ8getTracevE5level       # Guard variable
    jne .LBB0_4
    leaq    _ZGVZ8getTracevE5level, %rdi
    callq   __cxa_guard_acquire
    cmpl    $0, %eax
    je  .LBB0_4
.Ltmp0:
    movl    $4, %eax
    movl    %eax, %edi
    callq   _Znwm                            # new
.Ltmp1:
    movq    %rax, -24(%rbp)         # 8-byte Spill
    jmp .LBB0_3
.LBB0_3:                                # %invoke.cont
    leaq    _ZGVZ8getTracevE5level, %rdi
    movq    -24(%rbp), %rax         # 8-byte Reload
    movq    -24(%rbp), %rcx         # 8-byte Reload
    movl    $0, (%rcx)
    movq    %rax, _ZZ8getTracevE5level
    callq   __cxa_guard_release
.LBB0_4:                                # %init.end
    movq    _ZZ8getTracevE5level, %rax
    addq    $32, %rsp
    popq    %rbp
    retq

Levelなどとして使用するようにコードを変更したintため、投稿したコードから取得するコードよりも簡単です。

于 2014-04-17T08:39:39.840 に答える
0

コードには明らかに未定義の動作があるため、多くを語ることは困難ですが、標準では、を呼び出すlevel前にヌル ポインターに初期化する必要がありますget_p。また、ローカルの static が 1 回だけ初期化されるようにするために、コンパイラは多かれ少なかれ追加のフラグを追加する必要があります。何かのようなもの:

static test_dummy* _p = nullptr;
static bool isInitialized = false;
if ( !isInitialized ) {
    _p = new test_dummy();
    isInitialized = true;
}

(実際、もちろん、上に示した初期化はゼロの初期化であり、何よりも先に発生します。また、巧妙なコンパイラは、最初に発生する明示的な初期化が_pヌル ポインターになることはあり得ないことを認識することができ_p、制御変数)

上記はスレッドセーフではありません。スレッドセーフにするには、シーケンス全体を保護する必要があります。(完全なミューテックスの必要性を回避するための多かれ少なかれ複雑なトリックもありますが、すべての場合において、へのすべてのアクセスはisInitializedアトミックでなければなりません。)

シーケンスが保護されていない場合、別のスレッドが書き込みを確認する順序は定義されていません。そのため、一部のスレッドは isInitializedtrue と見なされていますが、まだ null ポインターが 表示されています_p

于 2014-04-17T09:29:30.353 に答える