254

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

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

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

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

你能解释一下编译器做了什么或者在运行时发生了什么吗?为什么结果为零?

某种免责声明:我绝对知道您不会(并且可能不应该)使用此代码。我知道我永远不会。尽管如此,我发现知道它为什么会以这种方式运行以及究竟发生了什么是很有趣的。

4

24 回答 24

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 增量和减量运算符

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

  • 如果 x 被归类为变量:

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

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


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

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

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;
于 2012-11-22T16:25:56.527 回答
195

运行代码的反汇编:

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。

在调试时使用它:

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

那么 postfix++ 发生了什么?

后缀++告诉我们想在评估之后增加操作数的值……每个人都知道……有点混淆的是“评估之后”的含义。

那么“评估后”是什么意思:

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

那么: 的含义是i += i++;什么?

它与i = i + i++;

评价顺序为:

  1. 存储 i + i(即 0 + 0)
  2. 增加 i (i 变为 1)
  3. 将第 1 步的值赋给 i(i 变为 0)

并不是说增量被丢弃。

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

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

评价顺序为:

  1. 存储 i(即 0)
  2. 增加 i (i 变为 1)
  3. 存储 step 1 + i 的值(即 0 + 1)
  4. 将第 3 步的值赋给 i(i 变为 1)
于 2012-11-22T17:33:17.760 回答
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

iei被更改了两次:一次由i++表达式更改,一次由+=语句更改。

但是+=语句的操作数是

  • i评估前的值i++(的左侧+=)和
  • ( 的右侧)i评估之前的值。i+++=
于 2012-11-22T16:31:29.583 回答
36

首先,i++返回 0。然后i加 1。最后i设置为初始值i0 加上i++返回的值,也就是 0。0 + 0 = 0。

于 2012-11-22T16:26:06.103 回答
32

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

// source code
i += i++;

// abstract syntax tree

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

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

右侧是后自增++运算符。它有一个操作数,i既可以作为值的来源,也可以作为存储值的位置。操作员评估i、发现0并因此将 a 存储1到该位置。它根据返回先验值0的语义返回先验值。

现在控制权又回到了+=操作员手中。它现在拥有完成其操作的所有信息。它知道存储结果的位置( 的存储位置i)以及先验值,并且它具有要添加到先验值的值,即0。所以,i以零结束。

与 Java 一样,C# 通过固定求值顺序净化了 C 语言的一个非常愚蠢的方面。从左到右,自下而上:编码人员可能期望的最明显的顺序。

于 2012-11-22T18:19:55.200 回答
30

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

于 2012-11-22T16:24:31.170 回答
17

后增量方法看起来像这样

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

所以基本上当你打电话时i++i是增量,但在你的情况下返回原始值,它是 0 被返回。

于 2012-11-22T16:36:27.310 回答
12

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

于 2012-11-22T16:24:07.617 回答
12

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

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

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

  1. i的当前值是多少?它是 0。存储它,以便我们可以将 i++ 的结果添加到它。
  2. 评估 i++(评估为 0,因为这是 i 的当前值)
  3. 加载存储的值并将步骤 2 的结果添加到其中。(加 0 到 0)

注意:在第 2 步结束时,i 的值实际上是 1。但是,在第 3 步中,您通过在 i 递增之前加载它的值来丢弃它。

与 i++ 不同,++i 返回递增的值。

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

于 2012-11-22T20:52:12.147 回答
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;
于 2012-11-23T05:34:50.820 回答
8

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

i += ++i;之前会做

于 2012-11-22T16:25:17.710 回答
8

++ 后缀i在递增之前求值,并且+=只求值i一次。

因此,0 + 0 = 0,asi在递增之前被评估和使用,因为使用的是后缀格式++。要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 (++)

于 2012-11-22T16:26:00.377 回答
8

C# 在做什么,以及困惑的“为什么”

我还预计该值为 1……但对此事的一些探索确实澄清了一些观点。

考虑以下方法:

    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# 在做什么……而且解释了为什么很多人对它感到困惑……包括我。

于 2012-11-23T19:52:20.253 回答
7

我一直记得的一个很好的助记符如下:

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

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

是 1,因为在它被之后的站立增加之前a是 1 。人们将此称为post fix 表示法。还有一个前缀表示法,情况正好相反:如果代表before,则表达式返回操作的值:++ a++

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

b是两个在这里。

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

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

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

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

Scott Meyers在“有效的 C++ 编程”中描述了这两种符号之间的区别。在内部,i++(postfix) 记住值iwas,并调用前缀表示法 ( ++i) 并返回旧值,i。这就是为什么您应该始终使用++iinfor循环(尽管我认为所有现代编译器都在转换i++++iinfor循环)。

于 2012-11-23T10:52:10.637 回答
6

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

好吧,在你们都烧我之前..

你们都回答了为什么i+=i++结果是可以且合乎逻辑的i=0

我很想对你的每一个答案都投反对票,但我计算出的声誉打击太高了..

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

但是结果如何??它是直观的结果吗?这是可以接受的结果吗?

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

你们都错了!

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

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

想要证明?你当然想要!

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

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

是否合理:i=i++i=i产生相同的结果i

whilet=i++t=i有不同的结果i

后操作是在语句评估之后应该发生的事情。
所以:

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 和许多其他来源告诉我们“嘿 - 这是未定义的!”

现在,在我继续之前,即使我给出的这组例子也没有得到任何人的支持或认可。然而,按照直觉和理性的方式,这应该是结果。

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

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

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

因此。

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

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

我没有找到任何 C# 文档来描述你们所有人都记录为该语言的正常或明确定义的行为。我发现的恰恰相反!

[从 MSDN 文档中复制后缀增量和减量运算符:++ 和 -- ]

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

请注意那些不能保证的词...

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

于 2014-03-26T09:13:46.710 回答
4

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

于 2012-11-22T16:27:16.890 回答
4

计算步骤如下:

  1. int i=0//初始化为0
  2. i+=i++ //方程
  3. i=i+i++ //通过编译器简化方程后
  4. i=0+i++ //i 值替换
  5. i=0+0 //i++ 为 0,如下所述
  6. i=0 //最终结果i=0

在这里,最初的值i是 0。WKTi++只不过是:首先使用该i值,然后将该i值递增 1。所以它在计算时使用该i值 0,i++然后将其递增 1。所以它产生一个值0。

于 2012-11-23T13:25:53.017 回答
3

有两种选择:

第一种选择:如果编译器读取语句如下,

i++;
i+=i;

那么结果是2。

为了

else if
i+=0;
i++;

结果是 1。

于 2012-11-22T16:29:07.797 回答
3

要非常小心:阅读 C常见问题解答:您正在尝试做的事情(混合赋值和++同一个变量)不仅未指定,而且也未定义(意味着编译器在评估时可以做任何事情!,不仅给出“合理”的结果)。

请阅读第 3 节。整个部分非常值得一读!尤其是 3.9,它解释了 unspecified 的含义。第 3.3 节简要总结了使用“i++”等可以做什么和不能做什么。

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

于 2012-11-23T12:39:08.080 回答
3

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

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

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

情况1:

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

早些时候我认为上面的代码类似于这个,所以乍一看答案是 1,而这个我的真正答案是 1。

案例二:

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

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

我希望这个能有一点帮助。谢谢

于 2014-09-09T13:25:21.733 回答
2

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

在我看来,它是按以下顺序发生的:

  1. i被评估为 0,导致i = 0 + 0增量操作i++“排队”,但 0 的分配i也尚未发生。
  2. 增量i++发生
  3. 上面的赋值i = 0发生了,有效地覆盖了#2(后增量)所做的任何事情。

现在,#2 可能永远不会真正发生(可能不会?),因为编译器可能意识到它没有任何作用,但这可能取决于编译器。无论哪种方式,其他更有见识的答案都表明结果是正确的并且符合 C# 标准,但没有定义 C/C++ 此处发生的情况。

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

++i此外,除非您这样做而不是i++我相信,否则您不会期望结果为 2 。

于 2012-11-27T19:13:58.887 回答
2

简单的说,

i++,将在“+=”运算符完成后将“i”加 1。

您想要的是 ++i,以便在执行“+=”运算符之前将“i”加 1。

于 2012-11-28T17:06:04.120 回答
0
i=0

i+=i

i=i+1

i=0;

然后将 1 添加到i.

我+=我++

所以在给加 1 之前,取值为 0。只有我们之前加 1 时,才i取值为0。ii

i+=++i

i=2
于 2012-11-23T16:04:14.567 回答
-4

答案是i1

让我们看看如何:

最初i=0;.

然后在i +=i++;根据 的值进行计算时,我们会有类似的东西0 +=0++;,所以根据运算符优先级0+=0将首先执行,结果将是0

然后增量运算符将应用 as 0++, as0+1和值iwill be 1

于 2012-11-23T07:33:20.633 回答