对于i = 0,为什么(i + = i ++)等于0?

时间:2012-11-22 16:22:17

标签: c# .net

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

static void Main(string[] args)
{
    int i = 0;
    i += i++;
    Console.WriteLine(i);
    Console.ReadLine();
}

i的结果是0.我预计2(正如我的一些同事所做的那样)。可能编译器会创建某种结构,导致i为零。

我期望2的原因是,在我的思路中,首先评估右手语句,然后用1递增i,而不是将其添加到i。因为我已经是1,所以它加1比1.所以1 + 1 = 2.显然这不是正在发生的事情。

您能解释一下编译器的作用或运行时会发生什么吗?为什么结果为零?

某种免责声明:我完全知道你不会(也可能不会)使用这段代码。我知道我永远不会。尽管如此,我发现知道为什么它以这种方式行动以及究竟发生了什么是很有趣的。

24 个答案:

答案 0 :(得分:425)

此:

int i = 0;
i += i++

你可以看到(以下是粗略的过度简化):

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 increment and decrement operators

  

x ++或x--形式的后缀增量或减量操作的运行时处理包括以下步骤:

     
      
  • 如果x被归类为变量:

         
        
    • x被评估以产生变量。
    •   
    • 保存x的值。
    •   
    • 调用所选运算符,并将保存的值x作为其参数。
    •   
    • 操作员返回的值存储在x。
    • 评估给出的位置   
    • x的保存值成为操作的结果。
    •   
  •   

请注意,由于order of precedence,后缀+++=之前发生,但结果最终未被使用(作为{{的前一个值)使用1}}。


i更彻底地分解为它所构成的部分需要知道i += i+++=都不是原子的(也就是说,两者都不是单个操作),即使他们看起来像他们。这些实现的方式涉及临时变量,操作发生前++的副本 - 每个操作一个。 (我将使用名称iiAdd分别用于iAssign++的临时变量。

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

+=

答案 1 :(得分:194)

反汇编正在运行的代码:

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)

等效代码

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

int i, tempVar1, tempVar2;
i = 0;
tempVar1 = i; // created due to postfix ++ operator
tempVar2 = i; // created due to += operator
++i;
i = tempVar1 + tempVar2;

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

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。

在debuging时使用它:

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

那么postfix ++会发生什么?

postfix ++告诉我们在评估之后我们想要增加操作数的值......每个人都知道......在评估之后“的含义有点混乱”

那么评估后的意味着:

  • 操作数的其他用法,在同一行代码中必须受到影响:
    • a = i++ + i第二个i受增量影响
    • Func(i++, i)第二个我受影响
  • 同一行上的其他用法尊重||&&等短路运营商:
    • (false && i++ != i) || i == 0第三个i不受i ++的影响,因为它没有被评估

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

i = i + i++;

相同

评估顺序为:

  1. 存储i + i(即0 + 0)
  2. 增量i(我变为1)
  3. 将步骤1的值分配给i(i变为0)
  4. 不是丢弃增量。

    含义是什么:i = i++ + i;

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

    评估顺序为:

    1. 存储i(即0)
    2. 增量i(我变为1)
    3. 步骤1 + i的存储值(即0 + 1)
    4. 将步骤3的值分配给i(i变为1)

答案 2 :(得分:61)

int i = 0;
i += i++;

评估如下:

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++表达式,另一次是+=语句。

但是+=语句的操作数是

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

答案 3 :(得分:36)

首先,i++返回0.然后i增加1.最后i设置为i的初始值,其为0加上值{{ 1}}返回,也是零。 0 + 0 = 0。

答案 4 :(得分:32)

这是抽象语法树的从左到右,自下而上的评估。从概念上讲,表达式的树是从上到下走的,但随着递归从底部弹回树,评估就会展开。

// source code
i += i++;

// abstract syntax tree

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

评估从考虑根节点+=开始。这是表达的主要组成部分。必须评估+=的左操作数以确定我们存储变量的位置,并获得前一个值为零。接下来,必须评估右侧。

右侧是后递增++运算符。它有一个操作数i,它既可以作为值的来源进行评估,也可以作为存储值的地方进行评估。运营商评估i,找到0,然后将1存储到该位置。它根据返回先前值的语义返回先前值0

现在控制权回到了+=运算符。它现在拥有完成其操作的所有信息。它知道存储结果的位置(i的存储位置)以及先前值,并且它具有要添加到先前值的值,即0。因此,i最终为零。

与Java一样,C#通过修复评估顺序来消除C语言的一个非常愚蠢的方面。从左到右,自下而上:编码员可能预期的最明显的顺序。

答案 5 :(得分:30)

因为i++首先返回值,然后递增它。但在i设置为1后,您将其设置为0。

答案 6 :(得分:17)

后增量方法看起来像这样

int ++(ref int i)
{
    int c = i;
    i = i + 1;
    return c;
}

所以基本上当你调用i++时,i是递增的,但在你的情况下返回原始值,它返回0。

答案 7 :(得分:12)

i ++表示:返回i的值,然后递增它。

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

现在,让我们添加i = 0作为起始条件。 i + = i ++现在评估如下:

  1. 我目前的价值是多少?它是0.存储它所以我们可以添加i ++的结果。
  2. 评估i ++(评估为0,因为它是i的当前值)
  3. 加载存储的值并将步骤2的结果添加到其中。 (加0到0)
  4. 注意:在步骤2结束时,i的值实际为1.但是,在步骤3中,您通过在i递增之前加载i的值来丢弃它。

    与i ++相反,++ i返回递增的值。

    因此,i + = ++我会给你1。

答案 8 :(得分:12)

简单回答

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;

答案 9 :(得分:11)

后固定增量运算符++为变量赋予表达式中的值,然后再次将指定的返回零(0)值的增量重写为i,覆盖增量值一(1),所以你得到零。您可以在 ++ Operator (MSDN)中阅读有关增量运算符的更多信息。

答案 10 :(得分:8)

++ 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(++)

答案 11 :(得分:8)

i += i++;将等于零,因为它之后会++

i += ++i;将在

之前完成

答案 12 :(得分:8)

C#正在做什么,以及混乱的“原因”

我还预计价值为1 ......但对此问题的一些探索确实澄清了一些观点。

Cosider以下方法:

    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

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

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

    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

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

这不仅解释了C#正在做什么......也解释了为什么很多人都对它感到困惑......包括我在内。

答案 13 :(得分:7)

一个很好的助记符,我一直记得以下内容:

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

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

为1,因为a之前 <{b}后<{1}}站点后增加了<{1}}。人们称之为帖子修复符号。还有一个 pre 修复符号,其中的情况正好相反:如果++代表之前的,则表达式返回后的值 操作:

a

++在这里是两个。

因此,对于您的代码,这意味着

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

b返回0(如上所述),因此int i = 0; i += (i++);

i++

Scott Meyers描述了“Effective C ++ programming”中这两个符号之间的区别。在内部,0 + 0 = 0(后缀)会记住值i += (++i); // Here 'i' would become two ,并调用前缀表示法(i++)并返回旧值i。这就是为什么你应该在++i循环中使用i的原因(尽管我认为所有现代编译器都在++i循环中将for转换为i++

答案 14 :(得分:6)

您问题的唯一答案是:因为未定义。

好的,在你们烧我之前......

你们都回答了为什么i+=i++是正确的,合乎逻辑的结果是i=0

我很想对你的每一个答案进行投票,但我计算得到的声誉太高了。

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

但结果是什么?这是直观的结果 - 是否可以接受结果?

你们每个人都看到了“赤裸裸的王”#34;并以某种方式接受它作为一个理性的国王。

你们都错了!

i+=i++;结果0未定义。

语言评估机制中的一个错误,如果你......或者甚至更糟!设计中的一个错误。

想要证明吗?当然你想要的!

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

现在这......是直观的结果!因为我们首先评估t为其分配了一个值,并且只有在评估和分配之后才发生了后期操作 - 理性不是吗?

合理的是:i=i++i=i会为i产生相同的结果吗?

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

后期操作是在语句评估之后发生的事情 因此:

int i=0;
i+=i++;

如果我们写的话应该是一样的:

int i=0;
i = i + i ++;

因此与:

相同
int i=0;
i= i + i;
i ++;

因此与:

相同
int i=0;
i = i + i;
i = i + 1;

任何不是1的结果都表明编译器中的错误或语言设计中的错误,如果我们采用理性思考 - 但MSDN和许多其他来源告诉我们&#34;嘿 - 这是未定义的!&#34;

现在,在我继续之前,即使我给出的这些例子也没有得到任何人的支持或承认。但是,根据直觉和理性的方式,这应该是结果。

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

如果它的编写方式不符合语言定义 - 这是一个错误!

为了完成我从维基百科复制了这个,Increment and decrement operators
由于递增/递减运算符修改了其操作数,因此在同一表达式中多次使用此类操作数会产生未定义的结果 。例如,在诸如x - ++ x之类的表达式中,不清楚应该以什么顺序执行减法和增量运算符。当编译器应用优化时,这样的情况会变得更糟,这可能导致操作的执行顺序与程序员的意图不同。

因此。

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

是.. - 即使C#编译器试图以某种方式对其进行标准化,也会产生无法预测的结果。

我没有找到任何关于C#的文档来描述你们所记录的行为,这些行为都是语言的正常或定义明确的行为。我找到的恰恰相反!

[copied from MSDN documentation for Postfix Increment and Decrement Operators: ++ and --]

将后缀运算符应用于函数参数时,参数的值无法保证在传递给函数之前递增或递减。有关详细信息,请参阅C ++标准中的1.9.17节。

请注意无法保证 ...

这些字词

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

答案 15 :(得分:4)

变量后的++运算符使其成为后缀增量。增量发生在语句中的所有其他内容之后,即添加和赋值。相反,如果你把++放在变量之前,它会在我的值被评估之前发生,并给你预期的答案。

答案 16 :(得分:4)

计算步骤如下:

  1. int i=0 //已初始化为0
  2. i+=i++ // Equation
  3. i=i+i++ //在通过编译器简化等式之后
  4. i=0+i++ // i值替换
  5. i=0+0 // i ++为0,如下所述
  6. i=0 //最终结果i = 0
  7. 此处,i的值最初为0。 WKT,i++只不过是:首先使用i值,然后将i值增加1.所以 它在计算i时使用i++值0,然后将其递增1。 所以它导致值为0.

答案 17 :(得分:3)

有两种选择:

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

i++;
i+=i;

然后结果是2。

有关

else if
i+=0;
i++;

结果是1。

答案 18 :(得分:3)

要非常小心:阅读C FAQ:你正在尝试做什么(混合分配和同一变量的++)不仅没有说明,而且也是未定义的(意思是在评估时,编译器可以做任何,而不仅仅给出“可推理”的结果。

请阅读section 3。整个部分非常值得一读!特别是3.9,这解释了未指明的含义。第3.3节简要总结了“i ++”等可以做什么和不能做什么。

根据编译器的内部结构,您可能会得到0,或2,或1,甚至其他任何内容!由于它未定义,所以他们可以这样做。

答案 19 :(得分:3)

上面的答案中有很多优秀的推理,我只做了一个小测试,想和你分享

int i = 0;
i+ = i++;

这里结果我显示0结果。 现在考虑以下情况:

案例1:

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

之前我认为上面的代码与此类似,所以首先看看答案是1,我真的回答这个问题是1。

案例2:

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

这里增量运算符不会进入执行路径,不像以前的情况,i ++有机会在添加之前执行。

我希望这会有所帮助。感谢

答案 20 :(得分:2)

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

让我觉得它按照这个顺序发生:

  1. i被评估为0,导致i = 0 + 0增量操作i++&#34;排队&#34;,但是0到i的分配没有&还没发生过。
  2. 增量i++发生
  3. 上面的作业i = 0发生了,有效地覆盖了#2(后增量)所做的任何事情。
  4. 现在,#2可能永远不会真正发生(可能不会?)因为编译器可能意识到它没有用处,但这可能与编译器有关。无论哪种方式,其他更有见识的答案都表明结果是正确的并且符合C#标准,但是没有定义C / C ++的内容。

    如何以及为什么超出了我的专业知识,但之前评估的右侧分配在后增量之后发生的事实可能是令人困惑的。

    此外,除非你相信++i而不是i++,否则你不会期望结果是2。

答案 21 :(得分:2)

简单地说,

i ++,将1添加到&#34; i&#34;在&#34; + =&#34;之后运营商已经完成。

你想要的是++ i,所以它会将1添加到&#34; i&#34;在&#34; + =&#34;之前运算符被执行。

答案 22 :(得分:0)

i=0

i+=i

i=i+1

i=0;

然后将1添加到i

i + = i ++

因此,在向i添加1之前,i取值为0.只有在我们之前加1时,i才会得到值0.

i+=++i

i=2

答案 23 :(得分:-4)

答案是i将是1

我们来看看如何:

最初i=0;

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

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