お世話になります。
C言語上でのstatic変数についてですが、
マルチスレッド環境における初期化は
VCでは保障されているのでしょうか?
例えば以下のような関数があり、
これをマルチスレッドで動く関数上で呼び出した場合、
strの初期化は保障されているのでしょうか?
void sub()
{
static char str[256];
strcpy( str, test );
printf( %s\n, str );
}
int __stdcall thread_func( void* p )
{
sub();
_endthreadex(0);
return 0;
}
これらに対して明記されている物があれば、
ご教授いただければと思います。
宜しくお願い致します。
いろいろと「モノ」によって「初期化」の定義が異なるのであるが・・・
ISO/IEC 14882:1998 のレベルで話をするなら
3.6.2 Initialization of non-local objects
6.7 Declaration statement の 4 項
あたりが C++ の言語仕様として決められている。
提示の例では static char str[256]; が zero-initialization されていることになり
この例での zero-initialization は保証されている、と言っていいだろう。
strcpy(str, test); は初期化ではないので複数スレッド間で並列実行されうる。
他にもいろいろな初期化がありうるのだが、どういう内容がお好みかな?
それによって話はまったく変わってくる。
C++ 仕様としてはマルチスレッドなどは定義されていないので
C++ 仕様書のレベルではスレッドがどーのこーのは言及されていない。
VC++ の仕様書としては見つかっていないなー。
俺が VC++ の生成するオブジェクトを読む限りでは dynamic-initialization の排他はさ
れていない。
dynamic-initialization が「1回だけ」行われなければならないのであれば自前で排他
すべし
早速の回答ありがとうございます。
> 6.7 Declaration statement の 4 項
ありがとうございます。
提示いただいた部分拝見しました。
静的記憶期間を持つ局所オブジェクトは、
一番最初に初期化されるという事で認識しました。
ただ
> 処理系は静的記憶域期間をもつオブジェクトを静的に初期化することが
> 許されているが、それと同じ条件で静的記憶域期間を持つ他の局所
> オブジェクトを早期に初期化してよい。
> そうしない場合そのようなオブジェクトの初期化は
> 宣言に初めて制御が渡った時点で行われる。(X3014 抜粋)
というのがちょっと良く理解できませんでした。
処理系によって初期化される局所的静的オブジェクトは
制御がはじめて移されたスレッドによっても
初期化されうる???とうことでしょうか?
だから、
> 「1回だけ」行われなければならないのであれば自前で排他すべし
ということ?
>他にもいろいろな初期化がありうるのだが、どういう内容がお好みかな
言語仕様上の範囲ではマルチスレッド環境におけるstatic変数の
初期化については環境依存だという認識だったのですが、
ではVC上での扱いは?というのが気になった次第です。
>strcpy(str, test); は初期化ではないので複数スレッド間で並列実行されうる。
実際のコード上では、変数 str の使用は、
変則的な文字列をこのテンポラリバッファに入れる事を前提としています。
この場合はstr の文字列の整合性を保つ為に排他が必要
という認識で良いでしょうか?
#本来のsub関数は呼び出す上位が、排他されていることを前提としています。
X3014 抜粋部分の疑問は 3.6.2-2 を読めと書いてあるので素直に読んでくれ。
初期化順序の決め方で初期値が違う例が挙げてある。
マルチスレッドの話を除外して、シングルスレッドの純粋抽象計算機があるとして:
ゼロ初期化および定数式による初期化は、ほぼ自明なので省略する。
動的初期化の例をいくつか挙げてみよう。
void func() { static int x=foo(); ...
これは 6.7-4 [その宣言に始めて制御がわたった時点で行われる]
つまり最初に func() が呼び出されたときに1回限り foo() が呼ばれることになる。
2度目の func() の呼び出しでは foo() は呼ばれない、っつこと。
void bar(int x) {
if (x) { static myclass_t m(baz(), ++quux); ...
であれば同じく、最初に bar() が引数 x!=0 で呼び出されたとき1回限り
a. baz() が呼ばれ、 ++quux が行われ (この順序は逆であってもよい)
b. この両者の副作用が確定した後 myclass_t のコンストラクタが起動する。
引数 x==0 で呼び出されたときは1回目でも初期化は行われない。
引数 x!=0 でも2回目の bar() の呼び出しでは初期化は行われない。
で、以下はマルチスレッドである場合
・そもそも要件として「初期化」を行ってよいのは1回だけ
というのであれば(そんな要件が無いのであれば以下の話は要らない)
・コンパイラ (VC++) はマルチスレッド条件下でその初期化自体を1回だけ行う
ようなコードは勝手に生成したりしない (俺は VC++6 でしか見てない)
ので、「マルチスレッド条件下でも初期化が1回だけ行われる」ようなコードを
自分で書かなければならない、っつこと。
# さもないとデストラクタが複数回呼ばれることになりえて・・・
> この場合はstr の文字列の整合性を保つ為に排他が必要
っていうか、そもそもの原則として
「マルチスレッド条件下で複数スレッド・プロセッサが同時に実行しうるコードで、
異なる値を保持しうる変数が static であってはならない」
のではないの?
> X3014 抜粋部分の疑問は 3.6.2-2
なるほど、処理系によって順序による
初期化を行っても良いのですね。
> ・そもそも要件として「初期化」を行ってよいのは1回だけ
> というのであれば(そんな要件が無いのであれば以下の話は要らない)
私としては、1回でも初期化が行われるのであれば、
良いと考えていました。
以下のように上位からの文字列が切り捨てられることを
想定していたので。。。
int sub( char* hoge )
{
static char str[256];
named_mutex_lock();
strncpy( str, hoge, sizeof(str)-1);
printf( %s\n, str );
named_mutex_unlock();
}
#今回のことから、sub関数内で別に排他を追加することにしました。
> ・コンパイラ (VC++) はマルチスレッド条件下でその初期化自体を1回だけ行う
> ようなコードは勝手に生成したりしない (俺は VC++6 でしか見てない)
> ので、「マルチスレッド条件下でも初期化が1回だけ行われる」ようなコードを
> 自分で書かなければならない、っつこと。
なるほど。。。以外に面倒なのですね。。。
> # さもないとデストラクタが複数回呼ばれることになりえて・・・
今回はCなのでこれについてはとりあえず、
置いておいてもいいかなと思います。
> っていうか、そもそもの原則として
>「マルチスレッド条件下で複数スレッド・プロセッサが同時に実行しうるコードで、
> 異なる値を保持しうる変数が static であってはならない」のではないの?
おっしゃることごもっともです。
現在私が提供しているモジュールを使ったアプリケーションが
アクセス違反を起こすという問題が起こっていまして、
一応原因は特定できたのですが、使用する側に先のようなコードがあり
簡易的なレビュを行ったのですが、そこで今回のことが気になった次第です。
#エラー原因は全く別でしたが。。。
とりあえず、一通り確認できましたので、
本件、これで解決とさせて頂きます。
追加情報などあれば宜しくお願い致します。
提示 sub() の str を使って何がしたいのか良くわからないんだけど
毎回 str(n)cpy しているのであれば、以前の値を保持しておく必然がないわけで
時間的性能的に高価なロック・アンロックロジックを組み込むくらいなら
str の static を外してしまうべきだろう。
実際のコードでは str が巨大でスタックに入りきらないというのであれば
ごく普通に new/delete に修正するだけの話。
当該関数の作者が static の意味を理解せぬままつけていたのであれば論外。
strcpy(str, hoge); は言語規格書でいうところの初期化ではない。
ただの実行式文。
スレッド1が sub(hoge)
スレッド2が sub(piyo)
のように(同時に)呼び出した場合 str が static であるなら
同一の str に対し strcpy が同時に行われて hoyo とか hige とか入りうるぜ。
んで C であれば dynamic-initialization は存在しないので
「初期化」に対してロック機構はいらない。
strcpy など実行文に対してはもちろん必要。