式の評価順序、奇妙な振る舞い


da  2010-05-08 00:49:05  No: 71598  IP: [192.*.*.*]

タイトルが分かりにくくてすみません。以下のページであるコードが議論されています。

http://forums.topcoder.com/?module=Thread&threadID=673205&start=0&mc=13

そのコードは次のようになっています。一部コメントを加えました。

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;

struct XYZ { int X,Y,Z; };
vector<XYZ> A;

int rec(int idx){
  int i = A.size();
  A.push_back(XYZ());
  if (idx >= 1000) return i;

  A[i].X = rec(idx+1);
  /*
    以下のように書き換えると、実行結果は正しくなる
    int x = rec(idx+1);
    A[i].X = x;
   */
  return i;
}

int main(){
  A.clear();
  rec(0);
  printf("A :");
  for (int i=0; i<5 && i<(int)A.size(); i++)
    printf(" %d",A[i].X);
  puts("");
  fflush(stdout);
}

これを手元の環境(Windows7)で実行してみると、
g++(MinGW)で実行するとクラッシュします。
Visual C++ 2010 Express の cl でコンパイルすると、実行結果は次のようになります。

A : 1 2 3 4 5

上のページを読むと(読み間違っているかも知れませんが)、評価順序に関係が
あるようなのですが…。英語があまりうまく読めないこともあり、
上のページの内容が理解できずにいます。
なぜクラッシュするのでしょうか?

編集 削除
YuO  2010-05-08 02:50:08  No: 71599  IP: [192.*.*.*]

リンク先をちょこっと読んだ上でのものなので、違っているかもしれせん。

クラッシュする原因は、A[i]の評価とrecの呼び出し順序の問題になると思います。
・A[i]よりも先にrecの呼び出しがあった場合
問題は起きない
・recの呼び出しより先にA[i]が評価された場合
recの中のpush_backの呼び出しにより、評価されたA[i]の参照が無効化されるので、クラッシュの可能性がある。
ということでしょう。

前者は、
> A[i].X = rec(idx+1);
は、
int _tempX = rec(idx+1);
XYZ& _tempXYZ = A[i];
_tempXYZ.X = _tempX;
であり、後者は、
XYZ& _tempXYZ = A[i];
int _tempX = rec(idx+1);
_tempXYZ.X = _tempX;
なのだと思います。
# XYZ * const _tempXYZ = &A[i];とした方が問題がわかりやすいかもしれません。


なかなか、説明するのが難しいですが……。

編集 削除
da  2010-05-08 04:15:28  No: 71600  IP: [192.*.*.*]

YuOさん、ありがとうございます。

なるほど、あ、そうか、push_back()の呼び出しが後になると、メモリの移動が
発生する(かもしれない)からなんですね。

ということは、予めreserve()で必要サイズを確保すれば問題は発生しなくなる
ということですよね? (なぜなら、push_back()呼び出しによるサイズの増加に
対して、メモリの移動が起こらなくなるから)。

実際に、A.reserve(10000)を前もって呼び出しておくと、今度は
クラッシュしなくなりました。

編集 削除