57

C++ 標準には、3.3.2、「宣言のポイント」にある「驚くべき」名前検索の半有名な例が含まれています。

int x = x;

これxはそれ自体で初期化されますが、これは (プリミティブ型であるため)初期化されていないため、不確定な値を持ちます (自動変数であると仮定します)

これは実際には未定義の動作ですか?

4.1「左辺値から右辺値への変換」によれば、初期化されていない値に対して左辺値から右辺値への変換を実行することは未定義の動作です。右手xはこの変換を受けますか? もしそうなら、例は実際に未定義の動作をしますか?

4

4 に答える 4

22

更新: コメントでの議論に続いて、この回答の最後にいくつかの証拠を追加しました。


免責事項この回答は推測に基づくものであることを認めます。一方、C++11 標準の現在の定式化では、より正式な回答が得られないようです。


この Q&Aのコンテキストでは、C++11 標準が、各言語構造で期待される値カテゴリを正式に指定していないことが明らかになりました。以下では、主に組み込み演算子に焦点を当てますが、問題は初期化子に関するものです。最終的には、演算子の場合について導き出した結論を初期化子の場合に拡張することになります。

組み込み演算子の場合、正式な仕様の欠如にもかかわらず、(非規範的な) 証拠は、意図された仕様が、値が必要な場合はどこでも、指定されていない場合に prvalues を期待できるようにすることであるという標準で見つかります。そうでなければ

たとえば、パラグラフ 3.10/1 のメモには次のように書かれています。

箇条 5 の各組み込み演算子の説明は、それが生成する値のカテゴリと期待されるオペランドの値のカテゴリを示しています。たとえば、組み込みの代入演算子は、左側のオペランドが左辺値であり、右側のオペランドが prvalue であると想定し、結果として左辺値を生成します。ユーザー定義演算子は関数であり、期待値と生成値のカテゴリは、パラメーターと戻り値の型によって決まります。

一方、代入演算子に関するセクション 5.17 では、これについて言及されていません。ただし、左辺値から右辺値への変換を実行する可能性については、注記 (パラグラフ 5.17/1) で再び言及されています。

したがって、関数呼び出しは、左辺値から右辺値への変換と、単一の複合代入演算子に関連する副作用との間に介在してはなりません。

もちろん、右辺値が期待されていない場合、このメモは無意味です。

リンクされた Q&A へのコメントでJohannes Schaubが指摘したように、別の証拠が 4/8 に見つかりました。

特定の変換が抑制されるコンテキストがいくつかあります。たとえば、左辺値から右辺値への変換は、単項 & 演算子のオペランドでは実行されません。特定の例外は、これらの演算子とコンテキストの説明に記載されています。

これは、特に指定されていない限り、組み込み演算子のすべてのオペランドで左辺値から右辺値への変換が実行されることを意味しているようです。これは、別段の指定がない限り、組み込み演算子のオペランドとして右辺値が期待されることを意味します。


推測:

初期化は割り当てではないため、演算子は議論に参加しませんが、仕様のこの領域は上記とまったく同じ問題の影響を受けているのではないかと思います。

この信念を裏付ける痕跡は、参照の初期化に関するパラグラフ 8.5.2/5 でも見つけることができます(左辺値初期化子式の値は必要ありません)。

通常の左辺値から右辺値への変換 (4.1)、配列からポインターへの変換 (4.2)、および関数からポインターへの標準変換 (4.3) は必要ないため、そのような左辺値への直接バインディングが行われる場合は抑制されます。

「通常」という言葉は、参照型ではないオブジェクトを初期化するときに、左辺値から右辺値への変換が適用されることを意味しているようです。

したがって、イニシャライザの期待値カテゴリに関する要件は (完全に欠落していない場合でも) 不適切に指定されていますが、提供された証拠に基づいて、意図した仕様が次のようであると仮定することは理にかなっていると思います。

言語構造で値が必要な場合は常に、特に指定されていない限り、prvalue が期待されます

この仮定の下では、あなたの例では左辺値から右辺値への変換が必要になり、未定義の動作につながります。


追加の証拠:

この推測を裏付けるさらなる証拠を提供するために、それが間違っていると仮定して、コピー初期化に左辺値から右辺値への変換が実際に必要とされないようにし、次のコードを検討してください (貢献してくれたjogojapanに感謝します)。

int y;
int x = y; // No UB
short t;
int u = t; // UB! (Do not like this non-uniformity, but could accept it)
int z;
z = x; // No UB (x is not uninitialized)
z = y; // UB! (Assuming assignment operators expect a prvalue, see above)
       // This would be very counterintuitive, since x == y

この不均一な動作は、私にはあまり意味がありません。IMO でもっと理にかなっているのは、値が必要な場合は常に prvalue が期待されるということです。

さらに、Jesse Goodが回答で正しく指摘しているように、C++ 標準の重要な段落は 8.5/16 です。

— それ以外の場合、初期化されるオブジェクトの初期値 は、初期化子式の (場合によっては変換された) 値です必要に応じて、標準変換 (第 4 節) を使用して、イニシャライザ式を目的のの cv 非修飾バージョンに変換します。ユーザー定義の変換は考慮されません。変換できない場合は、初期化の形式が正しくありません。[ 注: 型「cv1 T」の式は、cv 修飾子 cv1 および cv2 とは関係なく、型「cv2 T」のオブジェクトを初期化できます。

ただし、ジェシーは主に「必要に応じて」ビットに焦点を当てていますが、「タイプ」という言葉も強調したいと思います。上記の段落では、変換先のtypeに変換するために「必要に応じて」標準の変換が使用されると述べていますが、カテゴリの変換については何も述べていません。

  1. 必要に応じてカテゴリ変換は実行されますか?
  2. それらは必要ですか?

2 番目の質問に関しては、回答の元の部分で説明したように、C++ 11 標準では現在、カテゴリ変換が必要かどうかを指定していません。 . したがって、明確な答えを出すことは不可能です。しかし、これが意図された仕様であると想定するのに十分な証拠を提供したので、答えは「はい」になると思います。

最初の質問に関しては、答えが「はい」であることは私には理にかなっているように思えます。「いいえ」の場合、明らかに正しいプログラムは不正な形式になります。

int y = 0;
int x = y; // y is lvalue, prvalue expected (assuming the conjecture is correct)

要約すると (A1 = "質問 1 への回答"、A2 = "質問 2 への回答"):

          | A2 = Yes   | A2 = No |
 ---------|------------|---------|
 A1 = Yes |     UB     |  No UB  | 
 A1 = No  | ill-formed |  No UB  |
 ---------------------------------

A2 が「いいえ」の場合、A1 は問題ではありません。UB はありませんが、最初の例の奇妙な状況 (たとえばz = y、UB を与えるが、しかし与えないz = x)x == yが現れます。一方、A2 が「はい」の場合、A1 が重要になります。それでも、それが「はい」であることを証明するのに十分な証拠が与えられています.

したがって、私の論文は、 A1 = "Yes" および A2 = "Yes" であり、Undefined Behavior が必要です。


さらなる証拠:

この欠陥レポート( Jesse Goodの好意による) は、この場合に未定義の動作を与えることを目的とした変更を提案しています。

[...] さらに、4.1 [conv.lval] パラグラフ 1 では、左辺値から右辺値への変換を「初期化されていないオブジェクト」に適用すると、未定義の動作が発生すると述べています。これは、不確定な値を持つオブジェクトの観点から言い換える必要があります

特に、パラグラフ 4.1 の提案された文言は次のように述べています。

左辺値から右辺値への変換が未評価のオペランドまたはその部分式 (第 5 節 [expr]) で発生した場合、参照されたオブジェクトに含まれる値はアクセスされません。それ以外の場合、変換の結果は次の規則に従って決定されます。

— T が (cv 修飾されている可能性がある) std::nullptr_t の場合、結果は null ポインター定数 (4.10 [conv.ptr]) です。

— それ以外の場合、glvalue T がクラス型を持っている場合、変換は glvalue から型 T の一時をコピー初期化し、変換の結果は一時の prvalue です。

— それ以外の場合、glvalue が参照するオブジェクトに無効なポインター値 (3.7.4.2 [basic.stc.dynamic.deallocation]、3.7.4.3 [basic.stc.dynamic.safety]) が含まれている場合、動作は実装定義です。 .

— それ以外の場合、T が (おそらく cv 修飾された) 符号なし文字型 (3.9.1 [basic.fundamental]) であり、glvalue が参照するオブジェクトに不定値が含まれている場合 (5.3.4 [expr.new]、8.5 [dcl.init]、12.6.2 [class.base.init])、およびそのオブジェクトに自動保存期間がないか、glvalue が単項 & 演算子のオペランドであったか、参照にバインドされていた場合、結果は不特定の値。[脚注: 左辺値から右辺値への変換がオブジェクトに適用されるたびに、値が異なる場合があります。レジスタに割り当てられた不定値を持つ unsigned char オブジェクトがトラップされる場合があります。—脚注終了]

それ以外の場合、glvalue が参照するオブジェクトに不確定な値が含まれている場合、動作は未定義です。

— それ以外の場合、glvalue が (おそらく cv 修飾された) 型 std::nullptr_t を持っている場合、prvalue の結果は null ポインター定数です (4.10 [conv.ptr])。それ以外の場合は、glvalue によって示されるオブジェクトに含まれる値が prvalue の結果です。

于 2013-02-20T23:20:02.330 に答える
-5

これは未定義の動作ではありません。初期化がないため、特定の値がわからないだけです。変数がグローバルで組み込み型の場合、コンパイラーは変数を正しい値に初期化します。変数がローカルであり、コンパイラーがそれを初期化しない場合、すべての変数が自分自身に初期化されるので、コンパイラーに依存しないでください。

于 2013-02-18T12:46:26.950 に答える
-6

動作は未定義ではありません。変数は初期化されておらず、初期化されていない値が開始される任意のランダム値のままです。clan'g テスト スーツの一例:

int test7b(int y) {
  int x = x; // expected-note{{variable 'x' is declared here}}
  if (y)
    x = 1;
  // Warn with "may be uninitialized" here (not "is sometimes uninitialized"),
  // since the self-initialization is intended to suppress a -Wuninitialized
  // warning.
  return x; // expected-warning{{variable 'x' may be uninitialized when used here}}
}

この場合のclang/test/Sema/uninit-variables.cテストで明示的に見つけることができます。

于 2013-02-26T23:16:47.873 に答える