ループ内で変数を宣言するのは良い方法でしょうか、それとも悪い方法でしょうか? 質問する

ループ内で変数を宣言するのは良い方法でしょうか、それとも悪い方法でしょうか? 質問する

質問 1:ループ内で変数を宣言するのは良い習慣でしょうか、それとも悪い習慣でしょうか?

他のスレッドを読んで、パフォーマンスの問題があるかどうか (ほとんどの人が「ない」と答えています)、また変数は常に使用される場所のできるだけ近くで宣言する必要があると書いてありました。私が疑問に思っているのは、これを避けるべきかどうか、または実際に好ましいかどうかです。

例:

for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

質問 2:ほとんどのコンパイラは、変数がすでに宣言されていることを認識し、その部分をスキップするだけですか、それとも実際に毎回メモリ内にその場所を作成しますか?

ベストアンサー1

これは素晴らしい練習です。

ループ内に変数を作成すると、そのスコープがループ内に制限されます。ループ外では参照も呼び出しもできません。

こちらです:

  • 変数名が少し「一般的な」場合(「i」など)、コードのどこかで同じ名前の別の変数と混在するリスクはありません(-WshadowGCC の警告命令を使用して軽減することもできます)。

  • コンパイラは変数のスコープがループ内に制限されていることを認識しているため、変数が誤って他の場所で参照された場合は適切なエラー メッセージを発行します。

  • 最後になりましたが、変数はループ外では使用できないことがコンパイラによって認識されるため、コンパイラによっていくつかの専用の最適化 (最も重要なレジスタ割り当て) をより効率的に実行できます。たとえば、後で再利用するために結果を保存する必要はありません。

要するに、そうするのは正しいのです。

ただし、変数は各ループ間で値を保持しないことが想定されていることに注意してください。このような場合は、毎回初期化する必要があります。ループを囲むより大きなブロックを作成し、ループ間で値を保持する必要がある変数を宣言するだけの目的を持つこともできます。これには通常、ループ カウンター自体が含まれます。

{
    int i, retainValue;
    for (i=0; i<N; i++)
    {
       int tmpValue;
       /* tmpValue is uninitialized */
       /* retainValue still has its previous value from previous loop */

       /* Do some stuff here */
    }
    /* Here, retainValue is still valid; tmpValue no longer */
}

質問 2 について: 関数が呼び出されると、変数は 1 回割り当てられます。実際、割り当ての観点からは、関数の先頭で変数を宣言するのと (ほぼ) 同じです。唯一の違いはスコープです。変数はループの外部では使用できません。変数が割り当てられず、空きスロット (スコープが終了した他の変数から) を再利用しているだけである可能性もあります。

スコープが制限され、より正確になると、最適化の精度も高まります。しかし、さらに重要なのは、コードの他の部分を読み取るときに心配する状態 (つまり変数) が少なくなり、コードがより安全になることです。

これはブロックの外でも当てはまりますif(){...}。通常、 の代わりに:

    int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

次のように書く方が安全です:

    (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

特にこのような小さな例では、違いは小さいように見えるかもしれません。しかし、より大きなコード ベースでは、これは役に立ちます。つまり、ブロックからブロックに値を転送するリスクがなくなるのです。resultそれぞれが厳密に独自のスコープに制限されるため、役割がより正確になります。レビュー担当者の観点からは、心配したり追跡したりする長距離状態変数が少なくなるため、はるかに便利です。f1()f2()result

コンパイラーも をより良くサポートします。将来、何らかの誤ったコード変更が行われた後、 がresultで適切に初期化されないと仮定しますf2()。2 番目のバージョンは単に動作を拒否し、コンパイル時に明確なエラー メッセージを表示します (実行時よりもはるかに優れています)。最初のバージョンは何も検出せず、 の結果は のf1()結果と混同され、単に 2 度目にテストされますf2()

補足情報

オープンソースツールCppチェック(C/C++ コードの静的解析ツール) は、変数の最適なスコープに関する優れたヒントを提供します。

割り当てに関するコメントへの返信: 上記のルールは C では当てはまりますが、一部の C++ クラスでは当てはまらない可能性があります。

標準の型と構造体の場合、変数のサイズはコンパイル時に判明します。C には「構築」というものがないので、関数が呼び出されると、変数のスペースは単純にスタックに割り当てられます (初期化は行われません)。ループ内で変数を宣言する場合、コストは「ゼロ」になるのはそのためです。

ただし、C++ クラスの場合、コンストラクターというものがありますが、これについてはあまり詳しくありません。コンパイラは同じスペースを再利用できるほど賢いので、割り当てはおそらく問題にはならないと思いますが、初期化はループの反復ごとに行われる可能性が高いです。

おすすめ記事