これらのコンストラクトがインクリメント前とインクリメント後の未定義の動作を使用しているのはなぜですか?

833
PiX 2009-06-04 23:17.
#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}

14 answers

573
unwind 2009-06-04 23:20.

Cには未定義の動作の概念があります。つまり、一部の言語構造は構文的に有効ですが、コードの実行時の動作を予測することはできません。

私の知る限り、この規格では、未定義の振る舞いの概念が存在する理由を明確に述べていません。私の考えでは、言語設計者がセマンティクスにある程度の余裕があることを望んでいたからです。つまり、すべての実装が整数オーバーフローをまったく同じ方法で処理する必要があり、深刻なパフォーマンスコストがかかる可能性が非常に高いため、動作をそのままにしました。未定義なので、整数オーバーフローを引き起こすコードを記述した場合、何が起こる可能性もあります。

それで、それを念頭に置いて、なぜこれらの「問題」があるのでしょうか?言語は、特定のものが未定義の振る舞いにつながることを明確に述べています。問題はありません。「すべき」ことはありません。関係する変数の1つが宣言されたときに未定義の動作が変更された場合volatile、それは何も証明または変更しません。それは未定義。あなたはその行動について推論することはできません。

あなたの最も興味深く見える例、

u = (u++);

は未定義の振る舞いの教科書の例です(シーケンスポイントに関するウィキペディアのエントリを参照してください)。

76
badp 2010-05-25 03:26.

コードの行をコンパイルして逆アセンブルするだけです。取得しているものがどれだけ正確に取得できるかを知りたい場合は、

これは、私が自分のマシンで行っていることと、私が考えていることです。

$ cat evil.c void evil(){ int i = 0; i+= i++ + ++i; } $ gcc evil.c -c -o evil.bin
$ gdb evil.bin (gdb) disassemble evil Dump of assembler code for function evil: 0x00000000 <+0>: push %ebp 0x00000001 <+1>: mov %esp,%ebp 0x00000003 <+3>: sub $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp) // i = 0 i = 0 0x0000000d <+13>: addl $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(私は... 0x00000014命令はある種のコンパイラ最適化だったと思いますか?)

66
Christoph 2009-06-04 23:35.

C99標準の関連部分は6.5式、§2だと思います

前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、格納される値を決定するために読み取り専用でなければなりません。

および6.5.16代入演算子、§4:

オペランドの評価の順序は指定されていません。代入演算子の結果を変更しようとしたり、次のシーケンスポイントの後でそれにアクセスしようとした場合、動作は定義されていません。

61
haccks 2015-06-27 14:27.

ここで引用されている回答のほとんどは、これらの構成の動作が未定義であることを強調するC標準から引用されています。これらの構成の動作が定義れていない理由を理解するために、 C11標準に照らして最初にこれらの用語を理解しましょう。

シーケンス:(5.1.2.3)

任意の二つの評価を与えられたABすれば、A前に配列決定されB、その後の実行はAの実行に先行するものB

シーケンスなし:

A前または後Bにシーケンスされていない場合、AおよびBはシーケンスされていません。

評価は、次の2つのいずれかになります。

  • 式の結果を計算する値の計算。そして
  • オブジェクトの変更である副作用

シーケンスポイント:

式の評価の間の配列点の存在AとはB意味し、そのすべての値の計算副作用に関連付けられているAすべての前に配列決定された値の計算副作用と関連しますB

今、質問に来ています、のような表現のために

int i = 1;
i = i++;

標準によると:

6.5式:

スカラーオブジェクトの副作用が、同じスカラーオブジェクトの異なる副作用、または同じスカラーオブジェクトの値を使用した値の計算に比べて順序付けされていない場合、動作は未定義です。[...]

したがって、同じオブジェクトに対する2つの副作用iは相互に順序付けられていないため、上記の式はUBを呼び出します。つまり、割り当てiによる副作用が++。による副作用の前に行われるか後に行われるかは順序付けられていません。
割り当てがインクリメントの前に行われるか後に行われるかに応じて、異なる結果が生成されます。これは、未定義の動作の場合の1つです。

i割り当ての左側をbeにil、割り当ての右側(式内i++)をbeirに名前変更すると、式は次のようになります。

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Postfix++演算子に関する重要なポイントは次のとおりです。

++変数の後に来るからといって、インクリメントが遅くなることを意味するわけではありません。コンパイラーが元の値が使用されていることを確認する限り、コンパイラーが好きなだけ早く増分を行うことができます。

これは、式il = ir++が次のいずれかとして評価できることを意味します。

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

または

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

二つの異なる結果が得られる1と、2その割当により副作用の配列に依存し++、したがってUBを呼び出します。

54
Shafik Yaghmour 2013-08-16 09:25.

それは両方の呼び出しので、行動は本当に説明できない、不特定の行動や未定義の動作を、我々はこのコードについての一般的な予測を立てることができないので、あなたが読んでいる場合が、OLVE Maudalのような仕事ディープCおよび未指定と未定義を、時にはあなたが良いを作ることができます特定のコンパイラと環境で非常に特定の場合に推測しますが、本番環境の近くでは行わないでください。

したがって、不特定の動作に移ると、ドラフトc99標準セクションの6.5パラグラフ3で次のように述べられています(強調鉱山):

演算子とオペランドのグループ化は、構文によって示されます。74)後で指定される場合を除き(関数呼び出し()、&&、||、?:、およびコンマ演算子の場合)、部分式の評価の順序との順序どの副作用が発生するかはどちらも特定されていません。

したがって、次のような行がある場合:

i = i++ + ++i;

私たちは、かどうかわからないi++か、++i最初に評価されます。これは主に、コンパイラに最適化のためのより良いオプションを提供するためです。

我々はまた、持っている未定義の動作をプログラム変数を変更する(しているので、ここでも同様にiu間に複数回、等。)シーケンスポイント。ドラフト標準セクション6.5パラグラフ2強調鉱山)から:

前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、格納される値を決定するためにのみ読み取られるものとします

次のコード例は未定義として引用されています。

i = ++i + 1;
a[i++] = i; 

これらすべての例で、コードは同じシーケンスポイントでオブジェクトを複数回変更しようとしています。これは;、次の各ケースで終了します。

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

不特定の動作は、セクションのドラフトc99標準で次のように定義されて3.4.4います。

不特定の値の使用、またはこの国際規格が2つ以上の可能性を提供し、どのような場合でも選択される要件を課さないその他の動作

そして、未定義の動作は、セクションで定義される3.4.3ように:

この国際規格が要件を課していない、移植不可能または誤ったプログラム構成または誤ったデータの使用時の動作

そして、次のことに注意してください。

考えられる未定義の動作は、予測できない結果で状況を完全に無視することから、環境に特徴的な文書化された方法で翻訳またはプログラムの実行中に動作すること(診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了(発行あり)にまで及びます。診断メッセージの)。

38
Steve Summit 2015-06-19 01:55.

これに答える別の方法は、シーケンスポイントの不可解な詳細や未定義の動作にとらわれるのではなく、単に質問することです。それら何を意味するのでしょうか。プログラマーは何をしようとしていましたか?

について尋ねられた最初の断片はi = i++ + ++i、私の本では明らかに非常識です。誰も実際のプログラムでそれを書くことはありません。それが何をするのかは明らかではありません。誰かがコーディングしようとしていた可能性のある、この特定の不自然な一連の操作をもたらすアルゴリズムは考えられません。そして、あなたと私にはそれが何をすべきかが明らかではないので、コンパイラーがそれが何をすべきかを理解できない場合でも、私の本では問題ありません。

2番目のフラグメント、i = i++は、少し理解しやすいです。誰かが明らかにiをインクリメントし、結果をiに割り当てようとしています。ただし、Cでこれを行うにはいくつかの方法があります。iに1を追加し、結果をiに戻す最も基本的な方法は、ほとんどすべてのプログラミング言語で同じです。

i = i + 1

もちろん、Cには便利なショートカットがあります。

i++

これは、「iに1を加算し、結果をiに戻す」ことを意味します。したがって、2つの寄せ集めを作成する場合は、

i = i++

私たちが実際に言っているのは、「iに1を追加し、結果をiに割り当て、結果をiに戻す」ということです。私たちは混乱しているので、コンパイラーも混乱してもあまり気になりません。

現実的には、これらのクレイジーな表現が書かれるのは、++がどのように機能するかを人為的に例として使用しているときだけです。そしてもちろん、++がどのように機能するかを理解することは重要です。しかし、++を使用するための1つの実用的なルールは、「++を使用する式が何を意味するのかが明確でない場合は、それを記述しないでください」です。

私たちはcomp.lang.cに数え切れないほどの時間を費やして、このような式とそれらが未定義である理由について議論していました。理由を本当に説明しようとする私の長い答えの2つは、Webにアーカイブされています。

  • なぜ標準はこれらが何をするかを定義しないのですか?
  • 演算子の優先順位によって評価の順序が決まりませんか?

質問3.8およびCFAQリストのセクション3の残りの質問も参照してください。

27
P.P 2015-12-31 10:26.

多くの場合、この質問は、次のようなコードに関連する質問の複製としてリンクされています。

printf("%d %d\n", i, i++);

または

printf("%d %d\n", ++i, i++);

または同様のバリアント。

これもすでに述べたように未定義の動作ですが、次のようなprintf()ステートメントと比較すると、関係する場合は微妙な違いがあります。

x = i++ + i++;

次のステートメントで:

printf("%d %d\n", ++i, i++);

の引数の評価順序は指定されprintf()ていません。つまり、式i++++iは任意の順序で評価できます。C11標準には、これに関連する説明がいくつかあります。

付録J、不特定の行動

関数呼び出し(6.5.2.2)で関数指定子、引数、および引数内の部分式が評価される順序。

3.4.4、不特定の動作

不特定の値の使用、またはこの国際規格が2つ以上の可能性を提供し、どのような場合でも選択される要件を課さないその他の動作。

例不特定の動作の例は、関数への引数が評価される順序です。

不特定の動作自体は問題ではありません。この例を考えてみましょう。

printf("%d %d\n", ++x, y++);

これは、あまりにもあり、不特定の行動を評価する順序ため++xy++規定されていません。しかし、それは完全に合法で有効な声明です。このステートメントに未定義の動作はありません。変更(++xおよびy++)は個別のオブジェクトに対して行われるためです。

次のステートメントをレンダリングするもの

printf("%d %d\n", ++i, i++);

未定義の動作これらの2つの式は修正するという事実である同じオブジェクトをi介在せずにシーケンスポイント


別の詳細は、ということであるコンマのprintf()コールに関与するが、セパレータではなくカンマ演算子

コンマ演算子はオペランドの評価の間にシーケンスポイントを導入するため、これは重要な違いです。これにより、次のことが有効になります。

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

コンマ演算子は、そのオペランドを左から右に評価し、最後のオペランドの値のみを生成します。そうではj = (++i, i++);++i増加iする6i++歩留まりの古い値i6に割り当てられています)j。その後、ポストインクリメントによるものにiなり7ます。

その場合はカンマ関数呼び出しでは、カンマ演算子であることをしました

printf("%d %d\n", ++i, i++);

問題にはなりません。ただし、ここのコンマ区切り文字であるため、未定義の動作が発生します


未定義の振る舞い不慣れな人は、すべてのCプログラマーが未定義の振る舞いについて知っておくべきことを読んで、Cの未定義の振る舞いの概念や他の多くの変形を理解することをお勧めします。

この投稿:未定義、未指定、および実装定義の動作も関連しています。

23
supercat 2012-12-06 08:30.

コンパイラやプロセッサが実際にそうする可能性は低いですが、C標準では、コンパイラが次のシーケンスで「i ++」を実装することは合法です。

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

そのようなことを効率的に行うためのハードウェアをサポートしているプロセッサはないと思いますが、そのような動作によってマルチスレッドコードが簡単になる状況は簡単に想像できます(たとえば、2つのスレッドが上記を実行しようとすると保証されます)シーケンスを同時に実行すると、i2つインクリメントされます)。将来のプロセッサがそのような機能を提供する可能性があることはまったく考えられません。

コンパイラがi++上記のように記述し(標準では合法)、式全体の評価全体に上記の命令を散在させる場合(これも合法)、他の命令の1つが発生したことに気付かなかった場合アクセスするiために、コンパイラがデッドロックする一連の命令を生成することは可能です(そして合法です)。確かに、コンパイラは、ほぼ確実に同じ変数がケースで問題を検出しますi両方の場所で使用されますが、ルーチンは二つのポインタへの参照を受け入れた場合pq、および使用(*p)して(*q)上記の式(というよりも使用してi2回)コンパイラが認識または同じオブジェクトのアドレスは、両方のために渡された場合に発生するデッドロックを回避するために必要とされないpとしますq

18
Antti Haapala 2017-03-27 04:58.

またはのような式の構文は有効ですが、C標準のshallに従わないため、これらの構造の動作定義されていません。C99 6.5p2:a = a++a++ + a++

  1. 前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。[72]さらに、前の値は、保存される値を決定するために読み取り専用でなければならない[73]。

脚注73がさらにあることを明確に

  1. この段落は、次のような未定義のステートメント式をレンダリングします。

    i = ++i + 1;
    a[i++] = i;
    

    許可しながら

    i = i + 1;
    a[i] = i;
    

さまざまなシーケンスポイントは、C11(およびC99)の付録Cにリストされています。

  1. 以下は、5.1.2.3で説明されているシーケンスポイントです。

    • 関数指定子の評価と、関数呼び出しと実際の呼び出しの実際の引数の間。(6.5.2.2)。
    • 次の演算子の第1オペランドと第2オペランドの評価の間:論理積&&(6.5.13); 論理OR || (6.5.14); カンマ、(6.5.17)。
    • 条件付きの第1オペランドの評価の間?:演算子と、2番目と3番目のオペランドのいずれかが評価されます(6.5.15)。
    • 完全な宣言者の終わり:宣言者(6.7.6);
    • 完全な式の評価と次に評価される完全な式の間。以下は完全な式です。複合リテラル(6.7.9)の一部ではない初期化子。式ステートメントの式(6.8.3); 選択ステートメントの制御式(ifまたはswitch)(6.8.4); whileまたはdoステートメントの制御式(6.8.5); forステートメント(6.8.5.3)の(オプションの)式のそれぞれ。returnステートメントの(オプションの)式(6.8.6.4)。
    • ライブラリ関数が戻る直前(7.1.4)。
    • フォーマットされた各入出力関数変換指定子(7.21.6、7.29.2)に関連付けられたアクションの後。
    • 比較関数の各呼び出しの直前と直後、および比較関数の呼び出しと、その呼び出しに引数として渡されたオブジェクトの移動の間(7.22.5)。

C11の同じ段落の文言は次のとおりです。

  1. スカラーオブジェクトの副作用が、同じスカラーオブジェクトの異なる副作用、または同じスカラーオブジェクトの値を使用した値の計算に比べて順序付けされていない場合、動作は定義されていません。式の部分式に許容される順序が複数ある場合、そのような順序付けられていない副作用がいずれかの順序で発生した場合の動作は定義されていません84)。

あなたは、例えばとGCCの最近のバージョンを使用して、プログラムの中で、このようなエラーを検出することができます-Wallし、-Werrorして、GCCが完全にあなたのプログラムをコンパイルすることを拒否します。以下は、gcc(Ubuntu 6.2.0-5ubuntu12)6.2.020161005の出力です。

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

重要な部分は知っているどのようなシーケンスポイントをある-そして何であるシーケンスポイントと何ではありません。たとえば、コンマ演算子はシーケンスポイントであるため、

j = (i ++, ++ i);

は明確に定義されており、i1ずつ増加し、古い値を生成し、その値を破棄します。次に、コンマ演算子で、副作用を解決します。次にi、1ずつインクリメントすると、結果の値が式の値になります。つまり、これは単に工夫された書き方でj = (i += 2)あり、これもまた「賢い」書き方です。

i += 2;
j = i;

ただし、,関数の引数リストにはないコンマ演算子、および別個の引数の評価の間にはシーケンスポイントは存在しません。代わりに、それらの評価は相互に順序付けられていません。したがって、関数呼び出し

int i = 0;
printf("%d %d\n", i++, ++i, i);

関数の引数の評価関数引数の間にシーケンスポイントがないため、の動作未定義です。したがって、の値は、前のシーケンスポイントと次のシーケンスポイントの間で、との両方によって2回変更されます。i++++iii++++i

14
Nikhil Vidhani 2014-09-12 02:36.

C標準では、変数は2つのシーケンスポイント間で最大1回だけ割り当てる必要があるとされています。たとえば、セミコロンはシーケンスポイントです。
したがって、フォームのすべてのステートメント:

i = i++;
i = i++ + ++i;

などがそのルールに違反します。規格はまた、振る舞いは未定義であり、不特定ではないと述べています。一部のコンパイラはこれらを検出して結果を生成しますが、これは標準ではありません。

ただし、2つの異なる変数を2つのシーケンスポイント間でインクリメントできます。

while(*src++ = *dst++);

上記は、文字列をコピー/分析する際の一般的なコーディング方法です。

11
TomOnTime 2015-04-08 17:20.

に https://stackoverflow.com/questions/29505280/incrementing-array-index-in-c 誰かが次のようなステートメントについて尋ねました:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

これは7を出力します... OPは6を出力することを期待していました。

++i増分は、計算の残りの部分の前に全て完全に保証するものではありません。実際、ここではコンパイラが異なれば結果も異なります。あなたが提供した例では、最初の2は++i、その後の値は、実行k[]の最後そして、読んだ++i後、k[]

num = k[i+1]+k[i+2] + k[i+3];
i += 3

最新のコンパイラはこれを非常にうまく最適化します。実際、おそらく最初に書いたコードよりも優れています(期待どおりに機能したと仮定します)。

6
Steve Summit 2018-08-17 01:54.

あなたの質問はおそらく「なぜこれらの構造はCで未定義の振る舞いをするのか?」ではなかったでしょう。あなたの質問はおそらく「なぜこのコード(を使用して++)が私に期待した値を与えなかったのですか?」であり、誰かがあなたの質問を重複としてマークし、あなたをここに送りました。

この回答は、その質問に答えようとします。コードで期待した回答が得られなかった理由と、期待どおりに機能しない式を認識(および回避)する方法を学ぶことができます。

C++--演算子の基本的な定義と、接頭辞形式++xと接尾辞形式の違いについて聞いたことがあると思いますx++。しかし、これらの演算子について考えるのは難しいので、理解を確実にするために、おそらくあなたは次のようなものを含む小さな小さなテストプログラムを書いたでしょう

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

しかし、驚いたことに、このプログラムはあなたが理解するのに役立ちませんでした-それはいくつかの奇妙な、予期しない、説明できない出力を出力++しました。

または、おそらくあなたは次のような理解しにくい表現を見ています

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

おそらく誰かがあなたにそのコードをパズルとして与えたのでしょう。このコードは、特に実行する場合にも意味がありません。2つの異なるコンパイラーでコンパイルして実行すると、2つの異なる答えが得られる可能性があります。どうしたの?どちらが正しいですか?(そして答えは、両方がそうであるか、どちらもそうではないということです。)

これまでに聞いたように、これらの式はすべて未定義です。つまり、C言語はそれらが何をするかについて保証しません。これは奇妙で驚くべき結果です。コンパイルして実行する限り、作成できるプログラムはすべて、一意で明確に定義された出力を生成すると考えたからです。しかし、未定義の振る舞いの場合はそうではありません。

式が未定義になる理由は何ですか?式には常に未定義が含ま++--ていますか?もちろんそうではありません。これらは便利な演算子であり、適切に使用すれば、完全に明確に定義されています。

定義されていない式について話しているのは、一度に多くのことが行われている場合、どの順序で処理が行われるかわからない場合ですが、順序が結果にとって重要な場合です。

この回答で使用した2つの例に戻りましょう。私が書いたとき

printf("%d %d %d\n", x, ++x, x++);

問題は、を呼び出す前にprintf、コンパイラがx最初の値、またはx++、または多分を計算する++xかどうかです。しかし、私たちにはわからないことがわかりました。Cには、関数の引数が左から右、右から左、またはその他の順序で評価されるという規則はありません。私たちは、コンパイラがどうなるかを言うことができないので、xまず、それから++x、それからx++、またはx++、その後++x、その後x、または他のいくつかのため。ただし、コンパイラが使用する順序に応じて、によって出力される結果が明らかに異なるため、順序は明らかに重要printfです。

このクレイジーな表現はどうですか?

x = x++ + ++x;

この式の問題は、xの値を変更する3つの異なる試行が含まれていることです。(1)x++パーツはxに1を追加し、新しい値をに格納し、;のx古い値を返そうとしxます。(2)++xパーツはxに1を加算し、新しい値をに格納し、;のx新しい値を返そうとしxます。(3)x =パーツは、他の2つの合計をxに戻そうとします。これらの3つの試行された割り当てのどれが「勝ち」ますか?3つの値のどれが実際に割り当てられxますか?繰り返しになりますが、おそらく驚くべきことに、Cには私たちに伝えるルールはありません。

優先順位、結合性、または左から右への評価によって、状況がどのような順序で発生するかがわかると想像するかもしれませんが、そうではありません。信じられないかもしれませんが、私の言葉を信じてください。もう一度言います。優先順位と結合性は、Cの式の評価順序のすべての側面を決定するわけではありません。特に、1つの式内に複数ある場合x優先順位や結合性などに新しい値を割り当てようとするさまざまな場所では、これらの試行のどれが最初に発生するか、最後に発生するか、または何かがわかりませ


それで、すべての背景と紹介が邪魔にならないように、すべてのプログラムが明確に定義されていることを確認したい場合、どの式を書くことができ、どの式を書くことができないのですか?

これらの式はすべて問題ありません。

y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;

これらの式はすべて未定義です。

x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);

そして最後の質問は、どの式が明確に定義されているか、そしてどの式が未定義であるかをどうやって見分けることができるかということです。

先に述べたように、未定義の式とは、一度に多くのことが行われている場合、どのような順序で発生するのかがわからない場合、および順序が重要な場合です。

  1. 2つ以上の異なる場所で変更(割り当て)されている変数が1つある場合、どの変更が最初に発生するかをどのようにして知ることができますか?
  2. ある場所で変更され、その値が別の場所で使用されている変数がある場合、その変数が古い値を使用しているか、新しい値を使用しているかをどのように知ることができますか?

#1の例として、式で

x = x++ + ++x;

`xを変更する3つの試みがあります。

#2の例として、式で

y = x + x++;

両方ともの値を使用し、xそれを変更します。

これが答えです。作成する式で、各変数が最大で1回変更されることを確認してください。変数が変更された場合は、その変数の値を別の場所で使用しようとしないでください。

5
alinsoar 2017-10-14 03:58.

計算のこの種では何が起こるかについての良い説明は、文書で提供されn1188からISO W14サイト。

アイデアを説明します。

この状況に適用される標準ISO9899の主なルールは6.5p2です。

前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、格納される値を決定するために読み取り専用でなければなりません。

以下のような式でシーケンスポイントがi=i++前にあるi=とした後i++

上で引用した論文では、プログラムが小さなボックスで形成されていると理解できると説明されています。各ボックスには、2つの連続するシーケンスポイント間の命令が含まれています。シーケンスポイントはi=i++、完全な式を区切る2つのシーケンスポイントがある場合、標準の付録Cで定義されています。このような式は、構文的にexpression-statementは、バッカス・ナウア形式の文法のエントリと同等です(文法は標準の付録Aに記載されています)。

したがって、ボックス内の指示の順序には明確な順序がありません。

i=i++

と解釈することができます

tmp = i
i=i+1
i = tmp

またはとして

tmp = i
i = tmp
i=i+1

コードを解釈するためのこれらすべての形式i=i++が有効であり、両方が異なる回答を生成するため、動作は未定義です。

したがって、シーケンスポイントは、プログラムを構成する各ボックスの最初と最後で確認できます[ボックスはCの原子単位です]。ボックス内では、すべての場合に命令の順序が定義されているわけではありません。その順序を変更すると、結果が変わる場合があります。

編集:

そのような曖昧さを説明するための他の良い情報源は、c-faqサイト(本としても出版されている)からのエントリー、すなわちこことこことここです。

3
Mohamed El-Nakib 2017-06-11 12:56.

その理由は、プログラムが未定義の動作を実行しているためです。問題は、C ++ 98標準に従って必要なシーケンスポイントがないため、評価順序にあります(C ++ 11の用語に従って、操作が前後にシーケンスされることはありません)。

ただし、1つのコンパイラに固執すると、関数呼び出しやポインタを追加しない限り、動作が永続的になり、動作がより乱雑になります。

  • したがって、最初にGCC:Nuwen MinGW 15 GCC 7.1を使用すると、次のようになります。

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2
    

    }

GCCはどのように機能しますか?右辺(RHS)のサブ式を左から右の順序で評価し、その値を左辺(LHS)に割り当てます。これはまさにJavaとC#が動作し、標準を定義する方法です。(はい、JavaおよびC#の同等のソフトウェアで動作が定義されています)。RHSステートメントの各サブ式を左から右の順序で1つずつ評価します。サブ式ごとに:++ c(プリインクリメント)が最初に評価され、次に値cが操作に使用され、次にポストインクリメントc ++)が使用されます。

GCC C ++によると:演算子

GCC C ++では、演算子の優先順位によって、個々の演算子が評価される順序が制御されます。

GCCが理解する定義済みの動作C ++の同等のコード:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

その後、我々は、に行くのVisual Studio。Visual Studio 2015では、次のことがわかります。

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Visual Studioはどのように機能しますか、別のアプローチを取ります。最初のパスですべてのプリインクリメント式を評価し、2番目のパスで操作で変数値を使用し、3番目のパスでRHSからLHSに割り当て、最後にすべてのパスを評価します。ワンパスでのポストインクリメント式。

したがって、Visual C ++が理解する定義済みの動作C ++と同等です。

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

Visual Studioのドキュメントには、評価の優先順位と順序が記載されています。

複数の演算子が一緒に表示される場合、それらは同等の優先順位を持ち、それらの結合性に従って評価されます。表の演算子については、Postfix演算子で始まるセクションで説明しています。

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

ねえNFL、ジョーバロウとカイラーマレーは女性の権利をサポートするために少しの助けを使うことができます

ねえNFL、ジョーバロウとカイラーマレーは女性の権利をサポートするために少しの助けを使うことができます

ジョー・バロウロー対ウェイド事件の転覆に対応するNFLは、言葉を言わないことで、腹立たしいが予測可能なPRの結果でした。

別の日、別のヒンジのないLIV記者会見

別の日、別のヒンジのないLIV記者会見

(lから)パット・ペレス、ブルックス・ケプカ、パトリック・リードサウジアラビアのLIVゴルフリーグのさらに別の信じられないほどの記者会見で、スポーツのファンはブルックス・ケプカからでたらめな吐き気と質問回避の驚異的なマスタークラスを受けました。パトリックリード、ブライソンデシャンボー、パットペレス、最近のPGAツアーの脱北者。

ミズ・マーベルの家族の帰郷は悪役よりも激しく打撃を与える

ミズ・マーベルの家族の帰郷は悪役よりも激しく打撃を与える

レッドダガーとマーベルさんがチームを組んでいます。

6億7500万ドルのビットコインローンのデフォルト後にすべての資産を清算するように命じられたスリーアローズキャピタル

6億7500万ドルのビットコインローンのデフォルト後にすべての資産を清算するように命じられたスリーアローズキャピタル

暗号業界最大の沈没船の1つであるスリーアローズキャピタルは、ついにその悲惨さから解放されています。火曜日に、不良債権ヘッジファンドは、債権者からの返済を要求する訴訟の高まりに応えて、バージンアイランド裁判所によって清算を命じられました彼らが3ACに行ったローン。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

Suffragettes Indicam #3: Junho

Suffragettes Indicam #3: Junho

Mais um mês se findando — e metade do ano de 2022 já passou. Sabe o que isso significa? Não, não é hora de verificar se você está cumprindo com suas resoluções de Ano Novo.

多元宇宙—Junø

多元宇宙—Junø

チェーン間アカウントがJunoに登場します。異なるブロックチェーン間でスマートコントラクトの構成可能性と真の相互運用性を提供します。

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「​​ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。

メインネットガイド— Arbitrum Odyssey Week 2

メインネットガイド— Arbitrum Odyssey Week 2

最新のアップデートを受け取るために私たちに従ってください。ニュースレター:https://www。

Language