2013-02-07 8 views
5

スレッドローカルストレージの初期値に関するMSDNの矛盾が見つかりました。 :Windowsのスレッドローカルストレージは値を初期化しますか?

スレッドが作成されると、システムはNULLに初期化されるTLSのLPVOID値の配列を割り当てます。

これは、TlsGetValueを同じインデックスのTlsSetValueを一度も呼び出さなかったスレッドから有効なインデックスで呼び出すと、NULLポインタを取得する必要があると私に信じています。

This pageは、しかし、こう述べています。

それはスレッドがTlsGetValueを呼び出す前のTlsSetValueを呼び出していることを確実にするために...プログラマ次第です。

これは、明示的にTlsSetValueで初期化されていない限り、TlsGetValueから返された値に依存できないことを示しています。

しかし同時にsecond pageも言って初期化ツーヌル挙動を強化:

をそれがまだその初期値を有するか、またはスレッドのでためTLSスロットに格納されたデータが0の値を有することができ

データがNULL(または0)に初期化されているという2つのステートメントと、値を読み取る前に明示的に初期化する必要があるということがあります。実験的には、値は自動的にヌルポインタに初期化されているように見えますが、私はちょうど幸運を得ているかどうか、これがいつも正しいかどうかを知る方法はありません。

私はDLL_THREAD_ATTACHに割り当てるだけのDLLを使用しないようにしています。

LPVOID pMyData = ::TlsGetValue(g_index); 
if (pMyData == nullptr) { 
    pMyData = /* some allocation and initialization*/; 
    // bail out if allocation or initialization failed 
    ::TlsSetValue(g_index, pMyData); 
} 
DoSomethingWith(pMyData); 

これは信頼できる安全なパターンですか?または、私はそれを読む前に各スレッドのスロットを明示的に初期化する必要がありますか?

更新:ドキュメントには、TlsAlloc zeros out the slots for the allocated indexと記載されています。したがって、スロットが以前にプログラムの別の部分で使用されていたかどうかは関係ありません。

答えて

10

システムがTLSに割り当てた初期値が0であると言われると、ドキュメントが参考になりすぎています。この声明は真実だが役に立たない。

理由は、アプリケーションがを呼び出してTLSスロットを解放できるため、スロットを割り当てるときに、そのスロットに最初に割り当てられた人であるという保証はありません。したがって、値がシステムによって割り当てられた最初の0か、スロットの前の所有者によって割り当てられた他のジャンク値かどうかは分かりません。

は考えてみましょう:

  • 成分AはTlsAllocを呼び出し、スロット1スロット1が割り当てられます、それはコンポーネントAがTlsSetValue(1, someValue)を呼び出す0
  • の初期値が含まれているので、使用されていません。
  • コンポーネントAは、TlsGetValue(1)を呼び出し、someValueを返します。
  • コンポーネントAが終了し、TlsFree(1)を呼び出します。
  • 成分Bは、1
  • 成分BはTlsGetValue(1)を呼び出し、それが

したがって成分Aによって残さごみ値であるため、バックsomeValueを取得スロットTlsAllocを呼び出して割り当てられます、それにプログラマーまでですTlsGetValueを呼び出す前にスレッドがTlsSetValueを呼び出すようにしてください。そうでなければ、TlsGetValueは残りのゴミを読み取るでしょう。

誤解を招くような文書は、「残量ゴミのデフォルト値はゼロです」と言っていますが、システムが初期化して最終的に与えられた時間の間にスロットに何が起こったのか分かりません。

エイドリアンのフォローアップは、再び状況を調査するために私を促し、そして成分BはTlsGetValue(1)を呼び出したときに、それがゼロになるように、成分Aは、TlsFree(1)を呼び出したときに実際にカーネルがスロットをゼロに。これは、TlsFree(1)の後にTlsSetValue(1)と呼ばれるコンポーネントAにバグがないことを前提としています。

+0

良い推論ですが、TlsAllocはスロットをゼロに再初期化するので、コンポーネントBはコンポーネントAによって残されたガベージ値ではなく0に戻ります。私はさらに詳細な答えを追加しました。 –

+0

@Adrian現在のスレッドのスロットを再初期化します。他のスレッドはまだねじ込まれている、と私は信じている。 [更新:他のスレッドは実際にねじ込まれていません。彼らはTlsFree時にはゼロになります。] –

+0

おかげさまで、ありがとうございました。私はTlsFreeで再初期化されているのを見て回ったが、まだ他のスレッドをチェックしていなかった。 –

1

私は矛盾は見ません。 2番目のページは、システムがすべてのTLSスロットを0に初期化するが、いくつかの基本的な境界チェックを超えて、特定のTLSインデックスに有効なデータが含まれているかどうかを知る方法がないことを伝えるだけです(0は、また、あなたが要求するインデックスがあなたが望むインデックスであり、有効なデータが含まれていることを確認することはあなた次第です。

+0

"スレッドがTlsGetValueを呼び出す前にTlsSetValueを呼び出していることを確認するのはプログラマの責任です。"は矛盾のように聞こえる部分です。 –

2

レイモンド陳氏の推理は完璧な意味を成していたので、私は昨日、彼の答えを受け入れたが、今日、私はTlsAllocについては、MSDNのドキュメントに別のキーの行を発見:

関数が成功すると、戻り値はTLSインデックスであります。 インデックスのスロットはゼロに初期化されます。

TlsAllocが本当にゼロにスロットを初期化しない場合は、そのスロットの前のユーザが残したゴミ値を心配する必要はありません。

void TlsExperiment() { 
    DWORD index1 = ::TlsAlloc(); 
    assert(index1 != TLS_OUT_OF_INDEXES); 
    LPVOID value1 = ::TlsGetValue(index1); 
    assert(value1 == 0); // Nobody else has used this slot yet. 
    value1 = reinterpret_cast<LPVOID>(0x1234ABCD); 
    ::TlsSetValue(index1, value1); 
    assert(value1 == ::TlsGetValue(index1)); 
    ::TlsFree(index1); 

    DWORD index2 = ::TlsAlloc(); 
    // There's nothing that requires TlsAlloc to give us back the recently freed slot, 
    // but it just so happens that it does, which is convenient for our experiment. 
    assert(index2 == index1); // If this assertion fails, the experiment is invalid. 

    LPVOID value2 = ::TlsGetValue(index2); 
    assert(value2 == 0); // If the TlsAlloc documentation is right, value2 == 0. 
         // If it's wrong, you'd expect value2 == 0x1234ABCD. 
} 

私は、Windows 7上で実験を実行し、32ビットと64ビットの両方のテスト・プログラムと、

VS 2010でコンパイルさ:TlsAllocは、実際にこのように振る舞うないことを確認するために、私は次のような実験を実行しました

結果は、TlsAllocが値を0に再初期化するという考え方をサポートしています。TlsAllocは現在のスレッドの値をゼロにするなどの不具合をしていると思われますが、ドキュメンテーションは明示的に「スロット」(複数形)あなたのスレッドがまだスロットを使用していない場合、その値は0になります。

更新:さらなる実験では、スロットを0に再初期化するのはTlsAllocではなくTlsFreeであることが示唆されています。したがって、Raymond氏が指摘しているように、のスロットを解放した後、別のコンポーネントがTlsSetValue と呼ばれるリスクがあります。これは他のコンポーネントのバグですが、コードに影響します。