关于C#:对于i=0,为什么(i+=i++)等于0?

For i = 0, why is (i += i++) equal to 0?

采用以下代码(可用作控制台应用程序):

1
2
3
4
5
6
7
static void Main(string[] args)
{
    int i = 0;
    i += i++;
    Console.WriteLine(i);
    Console.ReadLine();
}

i的结果为0。我期望2(就像我的一些同事那样)。可能编译器创建了某种结构,导致i为零。

我期望2的原因是,在我的思路中,右边的语句将首先被评估,用1递增i。因为我已经是1,所以它是1对1。所以1+1=2。很明显,这不是正在发生的事情。

你能解释编译器是做什么的吗,或者在运行时会发生什么?为什么结果为零?

某种免责声明:我绝对知道您不会(也可能不应该)使用此代码。我知道我永远不会。然而,我发现很有趣的是,我知道它为什么会这样做,以及到底发生了什么。


这是:

1
2
int i = 0;
i += i++

正如你所做的(以下是一个过于简单化的问题):

1
2
3
int i = 0;
i = i + i; // i=0 because the ++ is a postfix operator and hasn't been executed
i + 1; // Note that you are discarding the calculation result

实际发生的事情比这更复杂-看一下msdn,7.5.9 postfix递增和递减操作符:

The run-time processing of a postfix increment or decrement operation of the form x++ or x-- consists of the following steps:

  • If x is classified as a variable:

    • x is evaluated to produce the variable.
    • The value of x is saved.
    • The selected operator is invoked with the saved value of x as its argument.
    • The value returned by the operator is stored in the location given by the evaluation of x.
    • The saved value of x becomes the result of the operation.

请注意,由于优先顺序的原因,后缀++出现在+=之前,但结果最终未被使用(使用了i的先前值)。

i += i++分解成它的组成部分需要知道+=++都不是原子的(也就是说,两者都不是单一的操作),即使它们看起来是原子的。这些方法的实现涉及到临时变量,在操作发生之前的i的副本-每个操作一个。(我将分别使用iAddiAssign作为+++=的临时变量)。

因此,更接近正在发生的事情是:

1
2
3
4
5
6
int i = 0;
int iAdd = i; // Copy of the current value of i, for ++
int iAssign = i; // Copy of the current value of i, for +=

i = i + 1; // i++ - Happens before += due to order of precedence
i = iAdd + iAssign;


运行代码的反汇编:

1
2
3
4
5
6
7
8
9
10
11
12
int i = 0;
  xor         edx, edx
  mov         dword ptr i, edx         // set i = 0
i += i++;
  mov         eax, dword ptr i         // set eax = i (=0)
  mov         dword ptr tempVar1, eax  // set tempVar1 = eax (=0)
  mov         eax, dword ptr i         // set eax = 0 ( again... why??? =\ )
  mov         dword ptr tempVar2, eax  // set tempVar2 = eax (=0)
  inc         dword ptr i              // set i = i+1 (=1)
  mov         eax, dword ptr tempVar1  // set eax = tempVar1 (=0)
  add         eax, dword ptr tempVar2  // set eax = eax+tempVar2 (=0)
  mov         dword ptr i, eax         // set i = eax (=0)

等效码

它编译为与以下代码相同的代码:

1
2
3
4
5
6
int i, tempVar1, tempVar2;
i = 0;
tempVar1 = i; // created due to postfix ++ operator
tempVar2 = i; // created due to += operator
++i;
i = tempVar1 + tempVar2;

第二个代码的反汇编(只是为了证明它们是相同的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int i, tempVar1, tempVar2;
i = 0;
    xor         edx, edx
    mov         dword ptr i, edx
tempVar1 = i; // created due to postfix ++ operator
    mov         eax, dword ptr i
    mov         dword ptr tempVar1, eax
tempVar2 = i; // created due to += operator
    mov         eax, dword ptr i
    mov         dword ptr tempVar2, eax
++i;
    inc         dword ptr i
i = tempVar1 + tempVar2;
    mov         eax, dword ptr tempVar1
    add         eax, dword ptr tempVar2
    mov         dword ptr i, eax

打开反汇编窗口

大多数人不知道,甚至不记得,他们可以使用Visual Studio反汇编窗口看到最终的内存汇编代码。它显示正在执行的机器代码,而不是CIL。

在调试时使用:

Debug (menu) -> Windows (submenu) -> Disassembly

那么,postfix++发生了什么?

postfix++告诉我们要在计算之后增加操作数的值…所有人都知道…有点困惑的是"评估后"的含义。

那么,"评估后"是什么意思呢?

  • 操作数在同一行代码上的其他用法必须受到影响:
    • a = i++ + i第二个i受增量影响
    • Func(i++, i)第二个I受到影响
  • 同一线路上的其他用途涉及短路操作器,如||&&
    • (false && i++ != i) || i == 0第三个i不受i++影响,因为它没有被评估

那么:i += i++;的意思是什么?

i = i + i++;相同

评估顺序为:

  • 存储I+I(即0+0)
  • 增量i(i变为1)
  • 将步骤1的值赋给i(i变为0)
  • 不是增量被丢弃。

    i = i++ + i;的意思是什么?

    这与前面的示例不同。第3个i受增量影响。

    评估顺序为:

  • 第一店(即0店)
  • 增量i(i变为1)
  • 存储步骤1+i的值(即0+1)
  • 将步骤3的值赋给i(i变为1)

  • 1
    2
    int i = 0;
    i += i++;

    评估如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Stack<int> stack = new Stack<int>();
    int i;

    // int i = 0;
    stack.Push(0);                   // push 0
    i = stack.Pop();                 // pop 0 --> i == 0

    // i += i++;
    stack.Push(i);                   // push 0
    stack.Push(i);                   // push 0
    stack.Push(i);                   // push 0
    stack.Push(1);                   // push 1
    i = stack.Pop() + stack.Pop();   // pop 0 and 1 --> i == 1
    i = stack.Pop() + stack.Pop();   // pop 0 and 0 --> i == 0

    也就是说,i改变了两次:一次由i++表达式改变,一次由+=语句改变。

    但是+=语句的操作数是

    • 评估i++前的值i(+=左侧)和
    • 评估i++前的值i(+=右侧)。


    首先,i++返回0。然后,i增加1。最后,将i设置为i的初始值,即0加上i++返回的值,即0。0±0=0。


    这只是抽象语法树的从左到右自下而上的评估。从概念上讲,表达式的树是自上而下遍历的,但是当递归从树的底部弹出时,计算就会展开。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // source code
    i += i++;

    // abstract syntax tree

         +=
        /  \
       i    ++ (post)
             \
             i

    评估首先考虑根节点+=。这是表达式的主要组成部分。必须对+=的左操作数进行计算,以确定存储变量的位置,并获得先验值为零。接下来,必须对右侧进行评估。

    右侧是后递增的++运算符。它有一个操作数,即i,它既是一个值的源,又是一个存储值的地方。操作员评估i,找到0,然后将1存储到该位置。根据返回优先值的语义,返回优先值0

    现在控制权回到了+=操作员手中。它现在拥有完成操作所需的所有信息。它知道存储结果的位置(i的存储位置)和优先值,并且它有附加到优先值的价值,即0。所以,i以零结束。

    与Java一样,C语言通过固定评估的顺序,对C语言的一个非常细微的方面进行了清理。从左到右,自下而上:编码人员可能期望的最明显的顺序。


    因为i++首先返回值,然后递增。但当我设为1后,你将它设回0。


    后增量方法如下所示

    1
    2
    3
    4
    5
    6
    int ++(ref int i)
    {
        int c = i;
        i = i + 1;
        return c;
    }

    所以基本上,当你调用i++时,i是递增的,但在你的例子中,原值是返回的,它是0。


    i++的意思是:返回i的值,然后递增。

    I+= i++表示:取i的当前值。添加i++的结果。

    现在,我们把i=0作为一个开始条件。i+=i++现在的计算方法如下:

  • 我现在的价值是多少?它是0。存储它,以便我们可以将I++的结果添加到其中。
  • 计算i++(计算为0,因为这是i的当前值)
  • 加载存储值并将步骤2的结果添加到该值中。(加0到0)
  • 注意:在步骤2的末尾,i的值实际上是1。但是,在步骤3中,您可以通过在i递增之前加载它的值来丢弃它。

    相对于i++,++i返回递增值。

    因此,我给你1。


    简单答案

    1
    2
    3
    4
    5
    6
    7
    8
    int i = 0;
    i += i++;
    // Translates to:
    i = i + 0; // because post increment returns the current value 0 of i
    // Before the above operation is set, i will be incremented to 1
    // Now i gets set after the increment,
    // so the original returned value of i will be taken.
    i = 0;


    后固定增量运算符++在表达式中为变量提供一个值,然后再次对i执行您分配的增量,该增量将返回零(0)值,从而覆盖递增的一(1),因此您将得到零。您可以在++operator(msdn)中阅读有关递增运算符的更多信息。


    C在做什么,以及困惑的"为什么"

    我还期望值是1…但对这一问题的一些探索确实澄清了一些要点。

    按以下方法排序:

    1
    2
    3
        static int SetSum(ref int a, int b) { return a += b; }

        static int Inc(ref int a) { return a++; }

    我希望i += i++SetSum(ref i, Inc(ref i))相同。此语句后i的值为1:

    1
    2
    3
    int i = 0;
    SetSum(ref i, Inc(ref i));
    Console.WriteLine(i); // i is 1

    但后来我得出另一个结论…i += i++实际上与i = i + i++相同。所以我创建了另一个类似的例子,使用这些函数:

    1
    2
    3
        static int Sum(int a, int b) { return a + b; }

        static int Set(ref int a, int b) { return a = b; }

    调用此Set(ref i, Sum(i, Inc(ref i)))后,i的值为0:

    1
    2
    3
    int i = 0;
    Set(ref i, Sum(i, Inc(ref i)));
    Console.WriteLine(i); // i is 0

    这不仅解释了C在做什么……但是为什么很多人对它感到困惑…包括我在内。


    ++postfix在递增之前对i进行评估,+=只对i进行一次评估。

    因此,0+0=0,因为i是在递增之前进行评估和使用的,因此使用++的后缀格式。要先递增i,请使用前缀形式(++i)。

    (另外,只需注意:您应该只得到1,因为0+(0+1)=1)

    参考文献:http://msdn.microsoft.com/en-us/library/sa7629ew.aspx(+=)http://msdn.microsoft.com/en-us/library/36x43w8w.aspx(++)


    i += i++;等于零,因为它在后面执行++

    i += ++i;会先做的


    我一直记得的一个好的助记法是:

    如果++在表达式后面,则返回它之前的值。所以下面的代码

    1
    2
    int a = 1;
    int b = a++;

    是1,因为a在增加之前是1,而a之后是11。人们称之为后缀符号。还有一个前缀表示法,情况正好相反:如果++位于前面,则表达式返回操作之后的值:

    1
    2
    int a = 1;
    int b = ++a;

    b是这里的两个。

    所以对于你的代码来说,这意味着

    1
    2
    int i = 0;
    i += (i++);

    i++返回0(如上所述),所以0 + 0 = 0返回。

    1
    i += (++i); // Here 'i' would become two

    Scott Meyers描述了这两个符号在"有效C++编程"中的区别。在内部,i++(postfix)记住i的值,调用前缀符号(++i并返回旧值i。这就是为什么你应该在for循环中使用++i(尽管我认为所有现代编译器都在把i++转换为++ifor循环中)。


    你的问题唯一正确的答案是:因为它是未定义的。

    好吧,在你们烧死我之前……

    你们都回答了为什么i+=i++是正常的,并且合乎逻辑地导致i=0

    我很想对你的每一个答案投反对票,但是我计算出的名声太高了。

    为什么我对你们这么生气?不是因为你的答案所解释的。
    我的意思是,我读到的每一个答案都做出了非凡的努力来解释不可能的事情,我鼓掌!< BR>

    但是结果是什么呢??是直观结果吗?是可接受的结果吗??

    你们每个人都看到了"裸王",不知何故都把它当作一个理性的国王。

    你们都错了!

    i+=i++;导致0未定义。

    语言评估机制中的一个错误,如果您愿意的话..甚至更糟!设计上的错误。

    需要证据吗?你当然想要!

    int t=0; int i=0; t+=i++; //t=0; i=1

    现在这个…是直观的结果!因为我们首先对t进行了评估,给它赋了一个值,只有在评估和分配之后,我们才有了操作后的发生,这是合理的,不是吗?

    i=i++i=ii产生相同的结果,这是合理的吗?

    t=i++t=ii有不同的结果。

    post操作应该在语句评估之后发生。
    因此:

    1
    2
    int i=0;
    i+=i++;

    如果我们写下:

    1
    2
    int i=0;
    i = i + i ++;

    因此,同:

    1
    2
    3
    int i=0;
    i= i + i;
    i ++;

    因此,同:

    1
    2
    3
    int i=0;
    i = i + i;
    i = i + 1;

    任何不是1的结果都表明编译器中有一个bug,或者如果我们进行理性思考的话,语言设计中有一个bug——然而,msdn和许多其他来源告诉我们"嘿——这是未定义的!"

    现在,在我继续之前,任何人都不支持或承认我给出的这组示例。然而,这正是根据直觉和理性的方式应该得到的结果。

    编码人员不应该知道如何编写或翻译程序集!

    如果它是以一种不尊重语言定义的方式编写的,那就是一个错误!

    最后,我从维基百科复制了这个,递增和递减操作符:
    由于递增/递减运算符修改其操作数,因此在同一表达式中多次使用此类操作数可能会产生未定义的结果。例如,在x等表达式中?++x,不清楚应该按什么顺序执行减法和增量运算符。当编译器应用优化时,这种情况会变得更糟,这可能导致操作的执行顺序与程序员预期的不同。

    因此。

    正确的答案是不应该使用这个!(因为它是未定义的!)

    对。。-即使C编译器试图以某种方式将其规范化,它也会产生不可预测的结果。

    我没有找到任何关于C描述行为的文档,所有人都被记录为语言的正常或定义良好的行为。我发现恰恰相反!

    [从msdn文档中复制了postfix递增和递减运算符:++和--]

    当对函数参数应用后缀运算符时,参数的值不保证在传递给函数之前递增或递减。请参阅C++标准中的1.1.17节以获取更多信息。

    注意那些没有保证的话…

    原谅我,如果这个答案看起来傲慢-我不是一个傲慢的人。我只是认为,成千上万的人来到这里学习,我读到的答案会误导他们,并会损害他们的逻辑和对这个主题的理解。


    变量后的+运算符使其成为后缀增量。递增发生在语句中所有其他内容之后,即添加和赋值。相反,如果您将++放在变量之前,它将在计算i的值之前发生,并给出预期的答案。


    计算步骤如下:

  • int i=0//初始化为0
  • i+=i++//方程
  • i=i+i++//编译器简化方程后
  • i=0+i++//i值替换
  • i=0+0//i++为0,如下所述
  • i=0//最终结果i=0
  • 这里,EDOCX1的初始值(0)是0。wkt,i++只不过是:首先使用i值,然后将i值增加1。所以它使用i值0,同时计算i++并将其递增1。所以它的结果是0。


    上面的答案有很多很好的推理,我只是做了一个小测试,想和大家分享一下

    1
    2
    int i = 0;
    i+ = i++;

    这里的结果是0。现在考虑以下情况:

    案例1:

    1
    i = i++ + i; //Answer 1

    之前我认为上面的代码类似于这个,所以乍一看答案是1,而我对这个的真正答案是1。

    案例2:

    1
    i = i + i++; //Answer 0 this resembles the question code.

    这里增量运算符不在执行路径中,与前面的情况不同,i++有机会在添加之前执行。

    我希望这能有所帮助。谢谢


    要非常小心:阅读C FAQ:你要做的(混合赋值和同一变量的++)不仅是未指定的,而且是未定义的(这意味着编译器在评估时可以做任何事情)。不仅给出"合理"的结果)。

    请阅读第3节。整个章节值得一读!尤其是3.9,这解释了未指明的含义。第3.3节简要总结了"i++"和类似的功能。

    根据编译器内部的不同,您可能会得到0、2、1,甚至其他任何东西!因为它是未定义的,所以他们可以这样做。


    有两种选择:

    第一个选项:如果编译器按如下方式读取语句,

    1
    2
    i++;
    i+=i;

    结果是2。

    为了

    1
    2
    3
    else if
    i+=0;
    i++;

    结果是1。


    简单地说,

    i++,将在"+="运算符完成后向"i"添加1。

    您需要的是++i,这样它将在执行"+="运算符之前向"i"添加1。


    希望从C编程101的角度来回答这个问题。

    在我看来,事情是按这样的顺序发生的:

  • i的计算结果为0,导致i = 0 + 0与增量操作i++"排队",但0对i的赋值也没有发生。
  • 增量i++发生
  • 上面的任务i = 0发生了,有效地覆盖了2(后增量)应该做的任何事情。
  • 现在,2可能永远不会发生(可能不会发生?)因为编译器可能意识到它没有任何作用,但这可能与编译器有关。无论哪种方式,其他更为丰富的答案都表明,结果是正确的,符合C标准,但它没有定义在这里发生的C/C++。

    如何以及为什么超出了我的专业知识范围,但是之前评估的右手边分配发生在后增量之后,这一事实可能让我困惑。

    此外,你不会期望结果是2不管,除非你做了++i而不是i++,我相信。


    1
    2
    3
    4
    5
    6
    7
    i=0

    i+=i

    i=i+1

    i=0;

    然后将1添加到EDOCX1中(0)。

    i+= i++

    因此,在将1添加到i之前,i的值为0。只有在前面加1,i才得到0。

    1
    2
    3
    i+=++i

    i=2

    答案是i将是1

    让我们看看如何:

    最初是i=0;

    然后根据值计算i +=i++;时,我们会得到类似0 +=0++;的值,因此根据运算符优先级,0+=0将首先执行,结果将是0

    然后将增量运算符应用于0++0+1i的值将为1