i =(i、++ i、1)+ 1;とは 行う?


174

未定義の動作とシーケンスポイントに関するこの回答を読んだ後、小さなプログラムを書きました。

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

出力は2です。ああ、私は減少が来るのを見ませんでした!ここで何が起きてるの?

また、上記のコードをコンパイルしているときに、次の警告が表示されました。

px.c:5:8:警告:コンマ式の左側のオペランドは効果がありません

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

どうして?しかし、おそらくそれは私の最初の質問の答えによって自動的に答えられます。


289
奇妙なことをしないでください。友達がいなくなります:(
Maroun

9
警告メッセージは、最初の質問に対する答えです。
Yu Hao

2
@gsamaras:いいえ。結果のは変更ではなく破棄されます。本当の答え:コンマ演算子はシーケンスポイントを作成します。
Karoly Horvath 2015年

3
@gsamaras正のスコアがある場合は気にする必要はありません。
LyingOnTheSky

9
注:最適化コンパイラーは簡単に実行できますprintf("2\n");
chux-Monica

回答:


256

(i, ++i, 1)では、使用されるコンマはコンマ演算子です

コンマ演算子(tokenで表される,)は、最初のオペランドを評価して結果を破棄し、次に2番目のオペランドを評価してこの値(および型)を返す2項演算子です。

最初のオペランドが破棄されるため、通常は、最初のオペランドに望ましい副作用がある場合のみ役立ちます。最初のオペランドへの副作用が発生しない場合、コンパイラーは式に関する警告を生成し、効果はありません。

したがって、上記の式では、左端iが評価され、その値は破棄されます。次に++i評価され、i1 ずつ増加し、式の値は++i破棄されますが、への副作用iは永続的です。次に1評価され、式の値はになります1

に相当

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

上記の式は完全に有効であり、コンマ演算子の左と右のオペランドの評価の間にシーケンスポイントがあるため、未定義の動作を引き起こさないことに注意しください。


1
最後の式は有効ですが、2番目の式++ iは未定義の動作ではありませんか?評価され、初期化されていない変数の値が事前にインクリメントされますが、これは正しくありませんか?または私は何かを逃していますか?
Koushik Shetty

2
@Koushik; iで初期化され5ます。宣言文を見てくださいint i = 5;
2015年

1
ああ、悪い。すみません、正直言ってそれを見てください。
Koushik Shetty

ここに誤りがあります:++ iはiを増分してから評価しますが、i ++はiを評価してから増分します。
クエンティンハヨット2015年

1
@QuentinHayot; 何?副作用は、式の評価後に発生します。の場合++i、この式は評価され、i増分され、この増分された値が式の値になります。の場合i++、この式が評価され、古い値はi式の値となり、式iの前のシーケンスポイントと次のシーケンスポイントの間の任意の時点で増分されます。
15年

62

C11、章6.5.17カンマ演算子からの引用

コンマ演算子の左のオペランドは、void式として評価されます。その評価と右のオペランドの評価の間にシーケンスポイントがあります。次に、右側のオペランドが評価されます。結果にはタイプと値があります。

したがって、あなたの場合、

(i, ++i, 1)

として評価されます

  1. i、void式として評価され、値は破棄されます
  2. ++i、void式として評価され、値は破棄されます
  3. 最後に1、値が返されました。

したがって、最後のステートメントは次のようになります

i = 1 + 1;

i到達し2ます。これで両方の質問に答えると思いますが、

  • i値2はどのように取得しますか?
  • なぜ警告メッセージがあるのですか?

注:FWIW、左側のオペランドの評価の後にシーケンスポイントが存在(i, ++i, 1)するため、一般に誤って考える可能性があるため、のような式はUBを呼び出しません。


+1 Sourav、これはの初期化がi明らかに効果がない理由を説明しています!しかし、コンマ演算子を知らない人にとってはそれほど明白ではなかったと思います(質問をする以外に、ヘルプを検索する方法を知りませんでした)。残念ですが、多くの反対票を獲得しました。他の回答を確認して、どちらを受け入れるかを決定します。ありがとう!いい答えですね。
gsamaras

なぜ私はなぜハックの答えを受け入れたのか説明しなければならないように感じます。それは本当に私の両方の質問に答えるので、私はあなたを受け入れる準備ができていました。ただし、私の質問のコメントを確認すると、なぜこれがUBを呼び出さないのか一見できない人もいます。haccksの回答は、いくつかの関連情報を提供します。もちろん、私は質問にリンクされているUBに関する答えを持っていますが、一部の人々はそれを見逃しているかもしれません。私の知らない場合は、私の方針に同意してください。:)
gsamaras

30
i = (i, ++i, 1) + 1;

段階的に分析してみましょう。

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

したがって、2を取得します。最後の割り当ては次のとおりです。

i = 2;

それが今上書きされる前にiにあったものは何でも。


これはコンマ演算子が原因で発生すると述べておくとよいでしょう。しかし、段階的な分析のための+1!いい答えですね。
gsamaras

説明が不十分で申し訳ありません。メモしかありません(...無視されます、あります...)。++iが結果に寄与しない主な理由を説明したかった。
dlask

私のforループは常に次のようになりますint i = 0; for( ;(++i, i<max); )
CoffeDeveloper

19

の結果

(i, ++i, 1)

です

1

ために

(i,++i,1) 

評価は、,オペレーターが評価された値を破棄し、最も適切な値を保持するように行われます。1

そう

i = 1 + 1 = 2

1
ええ、私もそう思いましたが、理由はわかりません!
gsamaras

@gsamarasは、コンマ演算子は前の用語を評価し、それを破棄するため(つまり、割り当てなどに使用しないため)
Marco A.

14

あなたはコンマ演算子のためのwikiページでいくつかの良い読書を見つけるでしょう。

基本的には

...最初のオペランドを評価して結果を破棄し、次に2番目のオペランドを評価してこの値(および型)を返します。

この意味は

(i, i++, 1)

次に、を評価しi、結果を破棄し、評価しi++、結果を破棄してから、を評価して返し1ます。


O_O地獄、その構文はC ++で有効ですか、その構文を必要とする場所がほとんどなかったことを覚えています(基本的に私は書きました:(void)exp; a= exp2;必要なだけですa = exp, exp2;
CoffeDeveloper

13

ここでは、コンマ演算子が何をしているのかを知る必要があります。

あなたの表現:

(i, ++i, 1)

最初の式iが評価され、2番目の式++iが評価され、3番目の式が式1全体に対して返されます。

したがって、結果は次のとおりi = 1 + 1です。

おまけの質問については、ご覧のとおり、最初の式iはまったく効果がないため、コンパイラーは不平を言います。


5

カンマの優先順位は「逆」です。これは、IBM(70s / 80s)の古い本やCのマニュアルから得られるものです。したがって、最後の「コマンド」は親式で使用されるものです。

現代のCではその使用は奇妙ですが、古いC(ANSI)では非常に興味深いです。

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

すべての操作(関数)は左から右に呼び出されますが、最後の式のみが結果として条件付き 'while'に使用されます。これにより、条件チェックの前に実行するコマンドの一意のブロックを保持するための「goto」の処理が防止されます。

編集:これにより、左側のオペランドのすべてのロジックを処理して論理結果を返す処理関数の呼び出しも回避されます。Cの過去には関数をインライン化していなかったことに注意してください。これにより、呼び出しのオーバーヘッドを回避できます。


Luciano、この回答へのリンクも取得しました:stackoverflow.com/questions/17902992/…
gsamaras

インライン関数が登場する90年代前半には、コードを最適化して整理するために、それをたくさん使用しました。
Luciano
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.