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

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

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

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

あなたの最も興味深い例は、

u = (u++);

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


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

これは、私が自分のマシンで得たものと、私が考えていることです:

$ 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 命令はある種のコンパイラ最適化だったと思いますか?)


C99 標準の関連部分は 6.5 式、§2

だと思います。

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