2

次のようなメソッドを持つC++プログラムがあります。

int myMethod(int* arr1, int* arr2, int* index)
{
    arr1--;        
    arr2--;
    int val = arr1[*index];
    int val2 = arr2[val];
    return doMoreThings(val);
}

最適化が有効(/ O2)の場合、最初のポインターがデクリメントされる最初の行は実行されません。最適化されたビルドと最適化されていないビルドを並べてデバッグし、最適化されていないプログラムが実行している間に、最適化されたビルドステップをデクリメントします。これにより、後でarr[*index]を使用して配列にアクセスしたときに動作に観察可能な違いが生じます。

アップデート

@stefaanvが指摘したように、デクリメントがデクリメントされたアクセスインデックスに変更された場合、コンパイラはデクリメントを実際に省略できます。したがって、省略されたデクリメントは、動作の違いを引き起こしているものではありません。代わりに、それを引き起こすマトリックスの使用に何かがあります。

さらに見てみると、行列の乗算を実行するネストされたループを含むメソッドに絞り込みました。メソッドの一部は次のようになります。a、wa、tの3つの配列が含まれます。メソッドの最初では、f2cトランスレータはデクリメントを使用して、Fortranで6x6の配列がdouble[36]cでフラットになるようにします。ただし、古いインデックスを使用できるようにするために、配列ポインターをマトリックス内の列数だけ戻します。

通常、このf2c変換プログラムでは、フラット配列はとして渡され&someArray[1]、メソッドは各配列を1つずつデクリメントすることから始まります。@Christophは、配列が宣言された範囲を超えてデクリメントされることはないため、これは有効である必要があると指摘しました。

このメソッドの場合、渡された配列は、要素へのポインタとしてさらに配列に渡されませんが&someArray[1]、ここでは、配列は固定サイズで宣言されたローカル静的配列でmat[36]あり、乗算メソッドに直接渡されます。

void test()
{
    double mat[36];
    ...
    mul(mat, .., ..)
}

void mul(double* a, double* t, double*wa, int M, int N, int K)
{
    // F2C array decrements.
    a -= (1+M); // E.g. decrement by seven for a[6x6]!
    t -= (1+N); 
    wa--;
    ...
    for (j = K; j <= M; ++j) {         
       for (i = 1; i <= N; ++i) {
          ii = K;
          wa[i] = 0.;          
          for (p = 1; p <= N; ++p) {
             wa[i] += t[p + i * t_dim1] * a[ii + j * a_dim1];
             ++ii;
          }
       }

       ii = K;      
       for (i = 1; i <= N; ++i) {
          a[ii + j * a_dim1] = wa[i];
          if (j > kn) {
             a[j + ii * a_dim1] = wa[i];
          }
          ++ii;
      }
    }
 }    

したがって、問題は次のとおりです。

これは、動作が未定義であり、f2cがここで行ったことを実行すると、最適化が機能しなくなる可能性があることを意味しますか?つまり、double [36]配列ポインターから7を減算し、配列内の正しい位置(オフセット7)のすべての項目にアクセスしますか?

編集:これはC FAQで見つかりましたが、これはここに当てはまりますか?

ポインタ演算は、ポインタが同じ割り当てられたメモリブロック内、またはその1つ先の架空の「終了」要素を指している場合にのみ定義されます。それ以外の場合、ポインタが逆参照されていなくても、動作は定義され ていません。....参照:K&R2Sec。5.3ページ 100、秒 5.4 pp。102-3、Sec。A7.7pp.205-6; ISO秒 6.3.6; 理論的根拠 3.2.2.3。

更新2:

デクリメントされたポインタではなく、デクリメントされたインデックスを使用して多次元配列で再コンパイルすると、

#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1 - 1 - a_dim1]

a_ref(1,2);

次に、このメソッドは、最適化に関係なく、同じ(期待される)出力を生成します。1つだけデクリメントされる1次元配列は、問題を引き起こさないように見えます。

プログラム内のすべての多次元配列を上記のアクセス方法を使用するように変更できますが、単一のdim配列は多すぎて手動で変更できないため、理想的には両方で機能するソリューションが必要です。

新しい質問:

  • f2cがポインタをいじるのではなく、この配列アクセス方法を使用するオプションはありますか?それはf2cの単純な変更であり、明確に定義されたコードを生成するように思われるので、それはすでにオプションであると思うでしょう。
  • この問題に対する他の解決策はありますか(最適化をスキップして、未定義の動作に依存しているにもかかわらず、プログラムが適切に動作することを期待する以外に)。
  • C ++コンパイラでできることはありますか?マネージC++プロジェクトとして、Microsoft C ++(2010)でコンパイルします。
4

2 に答える 2

5

オプティマイザは、観察可能な動作の変更がないことのみを確認する必要があるため、デクリメントを実行せず、デクリメントされたインデックスを使用してデータにアクセスすることを選択できます (余分なオフセットはオペコードの一部になる可能性があります)。配列へのポインタ。配列が実際にどのようにアクセスされているか、またオプティマイザが実際にエラーを引き起こしているかどうかはわかりません。そのため、推測することしかできません。

しかし、slartibartfast が既に述べたように、それは未定義の動作であり、int val = arr1[*index-1];*index > 0 であることを確認した後にデクリメントを置き換える必要があります

于 2012-06-05T10:59:56.130 に答える
1

配列へのポインターを指定された配列範囲外に移動してからアクセスすることは (完全な式は範囲内に戻りますが)、未定義の動作です。それにもかかわらず、ほぼすべての実装で、このコードは意図したとおりに機能するはずです。おそらく、arr1[] から int を取得するアセンブラー命令に暗黙のプリデクリメントがあるのでしょうか? デバッガーでデクリメントが表示されないからといって、それが存在しないとは限りません。識別値を書き込んで、正しい要素がアクセスされているかどうかを確認します。

于 2012-06-05T10:47:53.767 に答える