なぜc = ++(a + b)はコンパイルエラーを起こすのですか?


111

調査した後、インクリメント演算子はオペランドに変更可能なデータオブジェクトが必要であることを読みました:https : //en.wikipedia.org/wiki/Increment_and_decrement_operators

このことから、(a+b)は一時的な整数であり、変更できないため、コンパイルエラーが発生すると思います。

この理解は正しいですか?問題を調査しようとしたのはこれが初めてだったので、何か問題があった場合は、アドバイスしてください。


35
それは研究の点では悪くありません。あなたは正しい軌道に乗っています。
StoryTeller-Unslander Monica

35
あなたはその表現が何をすることを期待していますか?
qrdl

4
C11標準6.5.3.1に従って:接頭辞インクリメントまたはデクリメント演算子のオペランドは、アトミック、修飾、または非修飾の実数型またはポインター型でなければならず、変更可能な左辺値でなければなりません
Christian Gibbons

10
1をaとbの間でどのように分配しますか?「配列のインデックスは0と1のどちらで開始するべきですか?0.5の妥協案は、適切な検討なしに拒否されました。」— Stan Kelly-Bootle
Andrew Morton

5
質問のフォローc = a + b + 1は、意図を明確にし、タイプするのが短いときに、なぜこれをしたいのかと思います。インクリメント/デクリメント演算子は2つのことを行います。1。それらとその引数は式を形成します(たとえばforループで使用できます)、2。引数を変更します。この例では、プロパティ1を使用していますが、プロパティ2は使用していません。変更した引数を破棄するためです。プロパティ2が不要で、式だけが必要な場合は、式を記述できます(例:x ++ではなくx + 1)。
Trevor

回答:


117

それは単なるルールであり、それがすべてであり、おそらく(1)Cコンパイラの記述を容易にし、(2)C標準委員会がそれを緩和することを誰も確信していません。

非公式に言えば、のように代入式の左側に表示できる++foo場合にのみ記述fooできますfoo = bar。書けa + b = barないので書けません++(a + b)

操作可能a + bな一時を生成できなかった本当の理由はありません。++その結果は式の値です++(a + b)


4
ポイント(1)は頭の爪に当たると思います。C ++での一時的な具体化のルールを見るだけで、お腹が空く可能性があります(ただし、それは強力ですが、言わなければなりません)。
StoryTeller-Unslander Monica

4
@StoryTeller:実際、私たちの最愛の言語C ++とは異なり、Cは比較的簡単にアセンブリにコンパイルされます。
バトシェバ2018年

29
IMHOの真の理由は次のとおり++です。何かを変更する副作用があり、変更しない場合は恐ろしい混乱になります。
aschepler

5
@dng:確かにそうです。これがlvalueとrvalueという用語が導入された理由ですが、現在(特にC ++の場合)よりも複雑になっています。たとえば、定数を左辺値にすることはできません。5= aのようなものは意味がありません。
バトシェバ

6
@Bathsheba 5 ++がコンパイルエラーを引き起こす理由
dng

40

セクション6.5.3.1のC11標準状態

プレフィックスのインクリメントまたはデクリメント演算子のオペランドは、アトミック、修飾、または非修飾の実数型またはポインター型であり、変更可能な左辺値でなければなりません

また、「変更可能な左辺値」については、セクション6.3.2.1のサブセクション1で説明しています。

左辺値は、オブジェクトを指定する可能性のある式(ボイド以外のオブジェクトタイプを含む)です。評価時に左辺値がオブジェクトを指定しない場合、動作は未定義です。オブジェクトが特定のタイプを持っていると言われる場合、タイプはオブジェクトを指定するために使用される左辺値によって指定されます。 変更可能な左辺値は、配列型を持たない左辺値であり、不完全な型を持たず、const修飾型を持たず、構造体または共用体である場合、メンバー(再帰的に、任意のメンバーを含む)を持ちませんまたは含まれるすべての集合体または共用体の要素)、const修飾型。

したがって、(a+b)は変更可能な左辺値ではないため、前置インクリメント演算子は使用できません。


1
これらの定義からの結論がありません...(a + b)はオブジェクトを潜在的に指定しないと言いたいのですが、これらの段落ではそれを許可していません。
hkBst

21

あなたは正しいです。++元の変数に新しい値を代入しようとします。だから、++aの値をとるa追加し、1それにして、その後にそれをバックに割り当てますa。あなたが言ったように、(a + b)は一時的な値であり、メモリアドレスが割り当てられた変数ではないため、割り当てを実行できません。


12

主にあなた自身の質問に答えたと思います。C.Gibbonsが述べたように、私はあなたの言い回しに小さな変更を加えて、「一時変数」を「rvalue」に置き換えるかもしれません。

変数、引数、一時変数などの用語は、Cのメモリモデルについて学習するにつれて、より明確になります(これは、すばらしい概要のように見えます:https : //www.geeksforgeeks.org/memory-layout-of-c-program/)。

始めたばかりでは「右辺値」という用語は不透明に思えるかもしれません。そのため、直感を理解する上で以下が役立つことを願っています。

左辺値/右辺値は、等号(代入演算子)のさまざまな側面について話している:左辺値=左側(小文字のL、「1」ではない)右辺値=右側

Cがメモリ(およびレジスタ)をどのように使用するかについて少し学ぶことは、区別が重要である理由を理解するのに役立ちます。では、幅広いブラシストローク、コンパイラは式(右辺値)の結果を計算する機械語命令のリストを作成し、置くその結果どこか(左辺値)を。次のコードフラグメントを処理するコンパイラを想像してください。

x = y * 3

アセンブリ疑似コードでは、次のようなおもちゃの例のようになります。

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

++演算子(およびその対応物)は、変更するための「どこかに」、基本的には左辺値として機能できるものは何でも必要です。

Cのメモリモデルを理解すると、引数が関数に渡される方法や、(最終的には)malloc()関数のような動的メモリ割り当てを処理する方法について、頭の中でより良いアイデアが得られるので役立ちます。同様の理由で、コンパイラーが何をしているかをよりよく理解するために、ある時点でいくつかの単純なアセンブリープログラミングを研究するかもしれません。また、gccを使用している場合は、-Sオプション「適切なコンパイルの段階の後で停止し、アセンブルしないでください。」興味深いかもしれません(ただし、小さなコードフラグメントで試すことをお勧めします)。

余談ですが、++命令は1969年から存在しています(ただし、Cの前身であるBで始まりました)。

(ケン・トンプソンの)観察結果は、++ xの変換がx = x + 1の変換よりも小さいことでした。

そのウィキペディアの参照に続いて、便宜上ここにリンクされているC言語の歴史についてのデニス・リッチー(「K&R C」の「R」)による興味深い記事を紹介します。http//www.bell-labs.com/ usr / dmr / www / chist.htmlここで「++」を検索できます。


6

その理由は、規格ではオペランドが左辺値である必要があるためです。式(a+b)は左辺値ではないため、増分演算子の適用は許可されていません。

さて、「OK、それは確かに理由ですが、実際にはそれ以外の*実際の*理由はありませんと言うかもしれませんが、残念ながら、オペレーターが実際に機能する方法の特定の表現は、そうである必要あります。

式++ Eは(E + = 1)と同等です。

明らかに、左辺値でないE += 1場合Eは記述できません。「Eを1ずつ増やす」と言って実行することもできるので、これは残念です。その場合、コンパイラーを少し複雑にする代わりに、左辺以外の値に演算子を適用することは(原則として)完全に可能です。

さて、定義は簡単に言い換えることができますが(元々はCではなくBの家宝でもあると思います)、そうすることで言語は根本的に以前のバージョンと互換性のないものに変更されます。考えられる利益はかなり小さいですが、考えられる影響は非常に大きいので、それは決して起こらなかったし、おそらく起こりそうもありません。

Cに加えてC ++を検討する場合(質問にはCのタグが付けられていますが、演算子のオーバーロードについての議論がありました)、ストーリーはさらに複雑になります。Cでは、これが当てはまる可能性があるとは想像しがたいですが、C ++では、結果(a+b)がまったくインクリメントできないか、インクリメントによって非常に大きな副作用(1を追加するだけではない)になる可能性があります。コンパイラーはこれに対処でき、問題のあるケースが発生したときに診断できる必要があります。左辺値については、まだチェックするのは簡単です。あなたがかわいそうなものに投げかける括弧の中のどんな無計画な表現についてもそうではありません。
これはそれができなかっ本当の理由ではありません 行われますが、これを実装した人々が、非常に少数の人々にほとんどメリットがないと約束するそのような機能を追加することに、必ずしも有頂天ではない理由を説明するのに役立ちます。



3

++は元の変数に値を与えようとしますが、(a + b)は一時的な値であるため、操作を実行できません。そして、それらは基本的にプログラミングを容易にするためのCプログラミング規約の規則です。それでおしまい。


2

++(a + b)式を実行すると、たとえば次のようになります。

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.