私はいくつかのコードをいじり回して、「なぜ」の理由がわからないものを見ました。
int i = 6;
int j;
int *ptr = &i;
int *ptr1 = &j
j = i++;
//now j == 6 and i == 7. Straightforward.
等号の左側に演算子を配置するとどうなるでしょうか。
++ptr = ptr1;
に相当
(ptr = ptr + 1) = ptr1;
一方
ptr++ = ptr1;
に相当
ptr = ptr + 1 = ptr1;
Postfixはコンパイルエラーを実行し、私はそれを取得します。代入演算子の左側に定数「ptr + 1」があります。けっこうだ。
接頭辞oneはコンパイルされ、C++で機能します。はい、ごちゃごちゃしていて、割り当てられていないメモリを扱っていると思いますが、機能してコンパイルされます。 Cではこれはコンパイルされず、「代入の左オペランドとして必要な左辺値」と同じエラーを返します。これは、2つの「=」演算子または「++ ptr」構文を使用してどのように記述されていても、発生します。
Cがそのような割り当てを処理する方法とC++がそれを処理する方法の違いは何ですか?
CとC++の両方で、x++
の結果は右辺値であるため、代入できません。
Cでは、++x
はx += 1
と同等です(C標準§6.5.3.1/p2。C標準の引用はすべてWG14 N1570に対するものです)。 C++では、x
がbool
でない場合、++x
はx += 1
と同等です(C++標準§5.3.2[expr.pre.incr]/p1; all C++標準の引用はWG21 N3936に対するものです。
Cでは、代入式の結果は右辺値です(C標準§6.5.16/ p3)。
代入演算子は、左のオペランドで指定されたオブジェクトに値を格納します。割り当て式は、割り当て後に左のオペランドの値を持っていますが、左辺値ではありません。
左辺値ではないため、代入できません:(C標準§6.5.16/ p2-これは制約であることに注意してください)
代入演算子は、左オペランドとして変更可能な左辺値を持つものとします。
C++では、代入式の結果は左辺値です(C++標準§5.17[expr.ass]/p1):
代入演算子(=)と複合代入演算子はすべて、右から左にグループ化されます。すべて、左オペランドとして変更可能な左辺値を必要とし、左オペランドを参照する左辺値を返します。
したがって、++ptr = ptr1;
はCの診断可能な制約違反ですが、C++の診断可能な規則に違反していません。
ただし、C++ 11より前の++ptr = ptr1;
は、2つの隣接するシーケンスポイント間でptr
を2回変更するため、未定義の動作をします。
C++ 11では、++ptr = ptr1
の動作が明確になりました。次のように書き直すと、より明確になります
(ptr += 1) = ptr1;
C++ 11以降、C++標準はそれを提供しています(§5.17[expr.ass]/p1)
すべての場合において、割り当ては、右と左のオペランドの値計算の後、割り当て式の値計算の前にシーケンスされます。不規則にシーケンスされた関数呼び出しに関しては、複合代入の操作は単一の評価です。
したがって、=
によって実行される割り当ては、ptr += 1
およびptr1
の値の計算後にシーケンス化されます。 +=
によって実行される割り当ては、ptr += 1
の値計算の前に順序付けられ、+=
によって必要とされるすべての値計算は、その割り当ての前に必ず順序付けられます。したがって、ここでのシーケンスは明確に定義されており、未定義の動作はありません。
Cでは、事前および事後の増分の結果は右辺値であり、右辺値に割り当てることができません。左辺値(も必要です:左辺値とCおよびC++の右辺値)。 draft C11 standard セクション6.5.2.4
に続くPostfixインクリメントおよびデクリメント演算子を見るとわかります(鉱山を強調):
result Postfix ++演算子のis the valueオペランドの。 [...]制約、型、変換、およびポインタに対する演算の影響については、加算演算子と複合代入の説明を参照してください。 [...]
したがって、ポストインクリメントの結果は値であり、これはrvalueの同義語であり、セクション6.5.16
代入演算子に移動することでこれを確認できます。制約と結果のさらなる理解のために、それは言う:
[...]割り当て式は、割り当て後に左のオペランドの値を持ちますしかし、左辺値ではありません。[...]
これにより、ポストインクリメントの結果がlvalueではないことがさらに確認されます。
プレインクリメントについては、セクション6.5.3.1
プレフィックスインクリメントおよびデクリメント演算子から確認できます。
[...]制約、タイプ、副作用、変換、およびポインタに対する操作の影響については、加算演算子と複合代入の説明を参照してください。
ポストインクリメントのように6.5.16
もポイントするため、Cでのプリインクリメントの結果もlvalueではありません。
C++では、ポストインクリメントもrvalueであり、より具体的にはprvalueセクション5.2.6
インクリメントおよびデクリメントに行くことでこれを確認できます。
[...] 結果はprvalueです。結果のタイプは、オペランドのタイプのcv非修飾バージョンです[...]
プリインクリメントに関しては、CとC++は異なります。 Cでは結果は右辺値ですが、C++では結果は左辺値です。これは++ptr = ptr1;
がC++では機能するがCでは機能しない理由を説明しています。
C++の場合、これは5.3.2
インクリメントおよびデクリメントで説明されています。
[...]結果は更新されたオペランドです。 それはlvalueですであり、オペランドがビットフィールドの場合はビットフィールドです。[...]
次のことを理解するには:
++ptr = ptr1;
c ++で明確に定義されているかどうかは不明ですが、C++ 11以前のバージョンとC++ 11の2つの異なるアプローチが必要です。
C++ 11より前のバージョンでは、同じシーケンスポイント内でオブジェクトを複数回変更しているため、この式は undefined behavior を呼び出します。これは、C++ 11以前のドラフト標準セクション5
式に移動すると確認できます。
特に明記しない限り、個々の演算子のオペランドと個々の式の部分式の評価の順序、および副作用が発生する順序は指定されていません。57)前のシーケンスポイントと次のシーケンスポイントの間で、スカラーオブジェクトはその保存された値は、式の評価によって最大で1回変更されます。さらに、以前の値は、保存される値を決定するためにのみアクセスされます。この段落の要件は、完全な式の部分式の許容可能な順序ごとに満たされるものとします。それ以外の場合の動作は未定義です。 [例:
i = v[i ++]; / / the behavior is undefined i = 7 , i++ , i ++; / / i becomes 9 i = ++ i + 1; / / the behavior is undefined i = i + 1; / / the value of i is incremented
—例を終了]
ptr
をインクリメントしてから、それに割り当てます。これは2つの変更であり、この場合、シーケンスポイントは、式の最後の;
の後に発生します。
C + 11の場合、 defect report 637:Sequencing rules and example disagree に移動する必要があります。
i = ++i + 1;
c ++ 11以前はこれは undefined behavior でしたが、C++ 11では明確に定義された動作になります。このレポートの説明は、私が見た中でも最高の説明の1つであり、何度も読んだことは啓発的であり、多くの概念を新しい観点から理解するのに役立ちました。
この式が明確に定義された動作になるためのロジックは次のとおりです。
代入の副作用は、LHSとRHSの両方の値の計算後にシーケンスする必要があります(5.17 [expr.ass]段落1)。
LHS(i)は左辺値なので、その値の計算にはiのアドレスの計算が含まれます。
RHS(++ i + 1)を値計算するには、最初に左辺値式++ iを値計算してから、結果に対して左辺値から右辺値への変換を行う必要があります。これにより、インクリメントの副作用が加算演算の計算の前に順序付けられ、それが代入の副作用の前に順序付けられることが保証されます。つまり、この式の明確な順序と最終的な値が得られます。
ロジックはやや似ています。
++ptr = ptr1;
LHSとRHSの値の計算は、割り当ての副作用の前にシーケンスされます。
RHSは左辺値なので、その値の計算には、ptr1のアドレスの計算が含まれます。
LHS(++ ptr)を値計算するには、最初に左辺値式++ ptrを値計算してから、結果に対して左辺値から右辺値への変換を行う必要があります。これにより、インクリメントの副作用が割り当ての副作用の前に順序付けられることが保証されます。つまり、この式の明確な順序と最終的な値が得られます。
注意
OPは言った:
はい、ごちゃごちゃしていて、割り当てられていないメモリを扱っていると思いますが、機能してコンパイルされます。
非配列オブジェクトへのポインターは、加法演算子のサイズが1の配列と見なされます。C++標準のドラフトを引用しますが、C11にはほぼ同じテキストがあります。セクション5.7
から加算演算子:
これらの演算子の目的では、非配列オブジェクトへのポインターは、オブジェクトのタイプをエレメントタイプとして、長さが1の配列の最初のエレメントへのポインターと同じように動作します。
さらに、ポインタの逆参照を行わない限り、配列の最後の1つ先を指すことが有効であることを通知します。
[...]ポインタオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の1つ後の場合、評価でオーバーフローは発生しません。それ以外の場合、動作は未定義です。
そう:
++ptr ;
まだ有効なポインタです。