7

まず第一に、ほとんどの最適化バグは、プログラミング エラーまたは最適化設定 (浮動小数点値、マルチスレッドの問題など) に応じて変化する可能性がある事実に依存していることが原因であることを知っています。

しかし、バグを見つけるのが非常に困難であり、最適化をオフにせずにこの種のエラーが発生するのを防ぐ方法があるかどうか、やや確信が持てません。何か不足していますか?これは本当にオプティマイザのバグでしょうか? 簡単な例を次に示します。

struct Data {
  int    a;
  int    b;
  double c;
};

struct Test {
  void optimizeMe();

  Data m_data;
};

void Test::optimizeMe() {
  Data * pData; // Note that this pointer is not initialized!

  bool first = true;

  for (int i = 0; i < 3; ++i) {
    if (first) {
      first = false;

      pData = &m_data;

      pData->a = i * 10;
      pData->b = i * pData->a;
      pData->c = pData->b / 2;
    } else {
      pData->a = ++i;
    } // end if
  } // end for
};

int main(int argc, char *argv[]) {
  Test test;
  test.optimizeMe();
  return 0;
}

もちろん、実際のプログラムにはこれ以外にもやるべきことがたくさんあります。しかし、それはすべて、m_data に直接アクセスする代わりに、(以前は初期化された) ポインターが使用されているという事実に要約されます。-partに十分なステートメントを追加するとすぐにif (first)、オプティマイザーはコードを次の行に沿って何かに変更するようです。

if (first) {
  first = false;

  // pData-assignment has been removed!

  m_data.a = i * 10;
  m_data.b = i * m_data.a;
  m_data.c = m_data.b / m_data.a;
} else {
  pData->a = ++i; // This will crash - pData is not set yet. 
} // end if

ご覧のとおり、不要なポインター逆参照をメンバー構造体への直接書き込みに置き換えます。elseただし、 -branchではこれを行いません。-代入も削除しpDataます。ポインターはまだ初期化されているため、プログラムは -branch でクラッシュしelseます。

もちろん、ここにはさまざまな改善点があるため、プログラマのせいにすることもできます。

  • ポインターを忘れて、オプティマイザーが行うことを行います-m_data直接使用します。
  • pData を nullptr に初期化します。これにより、オプティマイザーelseは、ポインターが割り当てられない場合に -branch が失敗することを認識します。少なくとも、私のテスト環境では問題を解決しているようです。
  • ループの前にポインターの割り当てを移動します (効果的に で初期化pData&m_dataます。これは、ポインターの代わりに参照になる可能性があります (適切な測定のために)。 pData はすべての場合に必要であるため、内部でこれを行う理由はありません)。ループ。

控えめに言っても、コードは明らかに臭いです。オプティマイザがこれを行っていることを「非難」しようとしているわけではありません。しかし、私は尋ねています:私は何を間違っていますか? プログラムは醜いかもしれませんが、有効なコードです...

C++/CLI と v110_xp-Toolset で VS2012 を使用していることを付け加えておきます。最適化は /O2 に設定されています。また、問題を本当に再現したい場合 (それはこの質問のポイントではありません)、プログラムの複雑さをいじる必要があることにも注意してください。これは非常に単純化された例であり、オプティマイザーはポインターの割り当てを削除しないことがあります。関数の背後に隠れる&m_dataことは「役立つ」ようです。

編集:

Q: コンパイラが提供されている例のように最適化していることをどのように知ることができますか?

A: 私はアセンブラを読むのが苦手ですが、アセンブラを見て、次のように動作していると思わせる 3 つの観察を行いました。

  1. 最適化が開始されるとすぐに (通常は割り当てを追加するとうまくいきます)、ポインターの割り当てには関連付けられたアセンブラー ステートメントがありません。また、宣言まで移動されていないため、実際には初期化されていないようです(少なくとも私には)。
  2. プログラムがクラッシュした場合、デバッガーは割り当てステートメントをスキップします。プログラムが問題なく実行される場合、デバッガーはそこで停止します。
  3. pDataデバッグ中に の内容と の内容を見ると、 -branch 内のすべての割り当てが有効であり、正しい値を受け取るm_dataことが明確に示されています。ポインター自体は、最初から持っていた初期化されていない同じ値をまだ指しています。したがって、実際にはポインターを使用して割り当てをまったく行っていないと想定する必要があります。ifm_datam_data

Q: i (ループ展開) と何か関係がありますか?

A: いいえ、実際のプログラムは実際には do { ... } while() を使用して SQL SELECT 結果セットをループするため、反復カウントは完全にランタイム固有であり、コンパイラによって事前に決定することはできません。

4

2 に答える 2