1

サッターはこれを言います:

「CとC++の低レベルの効率の伝統では、明示的に行わない限り、コンパイラは変数を初期化する必要がないことがよくあります(たとえば、ローカル変数、コンストラクタ初期化子リストから省略されたメンバーを忘れた場合)。」

なぜコンパイラがint32やfloatなどのプリミティブを初期化しないのか疑問に思っていました。コンパイラが初期化するとパフォーマンスが低下しますか?間違ったコードよりも良いはずです。

4

4 に答える 4

3

実際、この議論は不完全です。初期化された変数には、効率と適切なデフォルトの欠如という 2 つの理由があります。

1) 効率

ほとんどの場合、C コンパイラが単に C からアセンブリへのトランスレータであり、最適化をまったく実行していなかった昔の名残です。

最近では、ほとんどの場合、冗長なストアを排除するスマート コンパイラとデッド ストアの排除があります。デモ:

int foo(int a) {
    int r = 0;
    r = a + 3;
    return r;
}

次のように変換されます。

define i32 @foo(i32 %a) nounwind uwtable readnone {
  %1 = add nsw i32 %a, 3
  ret i32 %1
}

それでも、よりスマートなコンパイラでも冗長ストアを排除できない場合があり、これが影響を与える可能性があります。後で断片的に初期化される大きな配列の場合...コンパイラは、すべての値が最終的に初期化されることを認識しないため、冗長な書き込みを削除しない場合があります。

int foo(int a) {
    int* r = new int[10]();
    for (unsigned i = 0; i <= a; ++i) {
        r[i] = i;
    }
    return r[a % 2];
}

以下の呼び出しに注意してください(値の初期化である呼び出しmemsetの接尾辞を付けることで必要でした)。不要なのに解消されませんでした。new()0

define i32 @_Z3fooi(i32 %a) uwtable {
  %1 = tail call noalias i8* @_Znam(i64 40)
  %2 = bitcast i8* %1 to i32*
  tail call void @llvm.memset.p0i8.i64(i8* %1, i8 0, i64 40, i32 4, i1 false)
  br label %3

; <label>:3                                       ; preds = %3, %0
  %i.01 = phi i32 [ 0, %0 ], [ %6, %3 ]
  %4 = zext i32 %i.01 to i64
  %5 = getelementptr inbounds i32* %2, i64 %4
  store i32 %i.01, i32* %5, align 4, !tbaa !0
  %6 = add i32 %i.01, 1
  %7 = icmp ugt i32 %6, %a
  br i1 %7, label %8, label %3

; <label>:8                                       ; preds = %3
  %9 = srem i32 %a, 2
  %10 = sext i32 %9 to i64
  %11 = getelementptr inbounds i32* %2, i64 %10
  %12 = load i32* %11, align 4, !tbaa !0
  ret i32 %12
}

2) デフォルト ?

もう 1 つの問題は、適切な値がないことです。afloatは完全に に初期化できますがNaN、整数はどうでしょうか? 値がないことを表す整数値はありません。まったくありません。0は (とりわけ) 候補の 1 つですが、最悪の候補の 1 つだと主張することもできます。これは非常に可能性の高い数字であり、したがって、目前のユースケースに対して特定の意味を持つ可能性があります。この意味がデフォルトであることに満足していますか?

思考の糧

最後に、単位化された変数には 1 つの優れた利点があります。それらは検出可能です。コンパイラは警告を発することがあり (それが十分に賢い場合)、Valgrind エラーを発生させます。これにより、論理的な問題が検出可能になり、検出された問題のみを修正できます。

もちろん、 などのセンチネル値NaNも同様に役立ちます。残念ながら...整数にはありません。

于 2012-09-27T08:24:47.563 に答える
2

初期化がパフォーマンスに影響を与える可能性がある方法は 2 つあります。

まず、変数の初期化には時間がかかります。確かに、単一の変数の場合はおそらく無視できますが、他の人が示唆しているように、多数の変数、配列などを合計することができます.

第二に、ゼロが合理的なデフォルトであると誰が言えるでしょうか? ゼロが有用なデフォルトであるすべての変数について、そうでない別の変数が存在する可能性があります。その場合、ゼロに初期化すると、実際に必要な値に変数を再初期化するオーバーヘッドがさらに発生します。デフォルトの初期化が行われない場合、基本的に初期化のオーバーヘッドは 1 回ではなく 2 回支払われます。これは、デフォルト値として何を選択しても、ゼロであろうとなかろうと、真であることに注意してください。

オーバーヘッドが存在する場合、通常は、初期化せず、初期化されていない変数への参照をコンパイラにキャッチさせる方が効率的です。

于 2012-09-27T01:38:09.087 に答える
0

基本的に、変数はデータを保持するように変更できるメモリ内の場所を参照します。単一化変数の場合、プログラムが知る必要があるのはこの場所がどこにあるかだけであり、コンパイラーは通常これを事前に把握するため、命令は必要ありません。ただし、初期化(たとえば、0)する場合、プログラムは追加の命令を使用して初期化する必要があります。

プログラムの起動中にmemsetを使用してヒープ全体をゼロにし、静的なものをすべて初期化するというアイデアもありますが、これは、読み取られる前に動的に設定されるものには必要ありません。これは、関数が呼び出されるたびにスタックフレームをゼロにする必要があるスタックベースの関数でも問題になります。つまり、特にスタックが新しく呼び出された関数で頻繁に上書きされる場合は、変数をデフォルトで未定義に設定する方がはるかに効率的です。

于 2012-09-27T01:26:34.647 に答える
0

-Wmaybe-uninitializedを使用してコンパイルし、調べます。これらは、コンパイラがプリミティブ初期化を最適化できない唯一の場所です。

ヒープは...

于 2012-09-27T01:29:39.710 に答える