为什么这些结构使用前置和后置增量未定义行为?

为什么这样子啊?

#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 %d\n", w++, ++w, w); // shouldn't this print 0 2 2
   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?}

C语言有未定义行为的概念,也就是说,有些语言结构在语法上是有效的,但是你不能预测代码运行时的行为。
据我所知,这个标准并没有明确说明为什么未定义行为的概念存在。在我看来,这仅仅是因为语言设计者希望在语义上有一些余地,而不是要求所有实现以完全相同的方式处理整数溢出,这很可能会造成严重的性能成本,他们只是留下了未定义的行为,所以如果你编写的代码导致整数溢出,一切皆有可能。
那么,考虑到这一点,为什么会出现这些“问题” ?语言清楚地表明,某些事情会导致未定义行为。没有问题,没有“应该”的含义。 如果当涉及的变量引入为 volatile时,未定义行为变量发生了变化,这不会证明或改变任何事情。它是未定义的; 您不能对行为进行推理。
看这个例子
u = (u++);
是未定义行为的一个典型例子

只需编译和反汇编你的代码行,如果你很想知道它到底是什么
下面是我看到的,以及我认为正在运行的:

$ cat evil.cvoid evil(){
  int i = 0;
  i+= i++ + ++i;}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin(gdb) disassemble evilDump 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>:  retEnd of assembler dump.