web-dev-qa-db-ja.com

c = ++(a + b)がコンパイルエラーになるのはなぜですか?

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

これより、(a+b)は一時的な整数で修正できないため、コンパイルエラーになります。

この理解は正しいですか?これは私が初めて問題を調査しようとした時なので、探したはずのものがあれば教えてください。

109
dng

これは単なる規則であり、すべてであり、(1)Cコンパイラを作成しやすくするため、および(2)C標準化委員会がそれを緩和することを誰も説得していないために存在する可能性があります。

非公式に言えば、++fooのように代入式の左側にfooが表示される場合にのみfoo = barを書くことができます。 a + b = barは書けないので、++(a + b)も書けません。

a + b++を操作するための一時的なものを生成できなかった理由は特にありません。その結果が式++(a + b)の値です。

117
Bathsheba

C11規格は6.5.3.1節に述べています

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

そして「修正可能左辺値」は6.3.2.1節1に記述されています

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

そのため、(a+b)は変更可能な左辺値ではないため、接頭辞インクリメント演算子には適していません。

40

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

21
Roee Gavirel

私はあなたが主にあなた自身の質問に答えたと思います。 C.Gibbonsが述べたように、私はあなたの表現を少し変更して "一時的な変数"を "rvalue"に置き換えます。

変数、引数、一時変数などの用語は、Cのメモリモデルについて学ぶにつれてより明確になります(これはNiceの概要のように見えます: https://www.geeksforgeeks.org/memory -cプログラムのレイアウト/ )。

「rvalue」という用語は、始めたばかりのときは不透明に見えるかもしれません。そのため、以下の説明が直感を深めるのに役立つことを願います。

左辺値/右辺値は等号(代入演算子)のさまざまな側面について説明しています。左辺値=左側( "L"の小文字ではなくL)右辺値=右側

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

x = y * 3

Assembly擬似コードでは、それはこのおもちゃの例のように見えるかもしれません

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で始まりました)。

(Ken Thompson氏の)観測によると、++ xの変換はx = x + 1の変換よりも小さかった。

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

12
jgreve

その理由は、規格ではオペランドが左辺値であることを要求しているからです。式(a+b)は左辺値ではないため、増分演算子を適用することはできません。

さて、"OK、それが理由ですが、実際にはそれ以外の*本当の*理由はありません"実際に動作しますになります。

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

Eが左辺値でない場合、E += 1を書くことはできません。"はEを1だけ増やします"そしてそうすることができるので、これは残念です。その場合、左辺値以外に演算子を適用することは(原則として)完全に可能ですが、コンパイラが少し複雑になります。

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

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

6
Damon

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

3

(a + b)は右辺値に評価されます。この値は増分できません。

3

++(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);
2
Jeet Parikh