lockにおける排他処理をしなかった場合の挙動

id:kosakとともに、実験を行った結果を。
例えば、マルチスレッドにおいて

volatile int num = 0;
func(){
  for(i = 0; i < 10000; i++)
    num++;
}
int main(){
  pthread_t t[THREAD];
  for(int i = 0; i < THREAD; i++)
    pthread_create(&t[i], NULL, func, NULL);
  for(int i = 0; i < THREAD; i++)
    pthread_join(&t[i], NULL);
  printf("%d\n", num);
}

とやると、numの値がTHREAD * 10000よりも小さくなることがあるのは常識かもしれない。numのメモリアクセスをlock等で排他処理してやらないと、読みだしと書き込みの間で同期バグが起きる可能性があるからだ。
例えばこれはどうだろうか。

volatile int num = 0;
int array[THREAD*10000];
func(){
  for(i = 0; i < 10000; i++){
    int tmp = num++;
    array[tmp]++;
  }
}

この場合も同様に、もちろんながらnumの値がTHREAD*10000よりも小さくなるのは事実だが、では0から順にarrayの値は歯抜けにならずに埋まるだろうか。
これが埋まらない可能性があるというのが今日の実験結果。なぜなら、tmp = num++のところで、tmpに代入を行うnumとincrementするnumを別々にloadする可能性があるからだ。これは少なくともVisual Studio 2008の吐いたアセンブリにおいて確認した。さらに問題は、numのvolatileがついている場合はこの部分の最適化が抑制されてしまい、最適化オプションO2においても歯抜けが発生する。ただし、volatileを取ってO2を付けると歯抜けにはならない。
これともう一つのバグ(プロトタイプ宣言をしなかったことによる自動型変換によるバグ)で、今日の一日の大半がつぶれた・・・・