请使用以下代码(可用作控制台应用程序):
static void Main(string[] args)
{
int i = 0;
i += i++;
Console.WriteLine(i);
Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)
结果i是0.我预计2(正如我的一些同事所做的那样).编译器可能会创建某种导致i为零的结构.
我期望2的原因是,在我的思路中,右手语句将首先被评估,用1递增i.比它被添加到i.因为我已经是1,所以它加1比1.所以1 + 1 = 2.显然这不是正在发生的事情.
你能解释一下编译器的作用或运行时会发生什么吗?为什么结果为零?
某种免责声明:我绝对知道你不会(也可能不会)使用这段代码.我知道我永远不会.尽管如此,我发现知道为什么它以这种方式行动以及究竟发生了什么是很有趣的.
Ode*_*ded 425
这个:
int i = 0;
i += i++
Run Code Online (Sandbox Code Playgroud)
你可以看到你做的事情(以下是粗略的过度简化):
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
Run Code Online (Sandbox Code Playgroud)
实际发生的事情比这更多 - 看一下MSDN,7.5.9后缀增量和减量运算符:
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;
Run Code Online (Sandbox Code Playgroud)
Mig*_*elo 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)
Run Code Online (Sandbox Code Playgroud)
它编译为与以下代码相同的代码:
int i, tempVar1, tempVar2;
i = 0;
tempVar1 = i; // created due to postfix ++ operator
tempVar2 = i; // created due to += operator
++i;
i = tempVar1 + tempVar2;
Run Code Online (Sandbox Code Playgroud)
反汇编第二个代码(只是为了证明它们是相同的)
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
Run Code Online (Sandbox Code Playgroud)
大多数人不知道,甚至不记得他们可以使用Visual Studio Disassembly窗口查看最终的内存中汇编代码.它显示正在执行的机器代码,它不是CIL.
在debuging时使用这个:
Debug (menu) -> Windows (submenu) -> Disassembly
postfix ++告诉我们在评估之后我们想要增加操作数的值...每个人都知道......有些混淆是"评估后"的含义.
那么"评估后"意味着什么:
a = i++ + i 第二个i受增量的影响Func(i++, i) 第二个我受到了影响||和&&:
(false && i++ != i) || i == 0 第三个i不受i ++的影响,因为它没有被评估那么含义是什么:i += i++;?
它是一样的 i = i + i++;
评估顺序是:
并不是说丢弃了增量.
是什么意思:i = i++ + i;?
这与前一个示例不同.第三个i受增量影响.
评估顺序是:
dtb*_*dtb 61
int i = 0;
i += i++;
Run Code Online (Sandbox Code Playgroud)
评估如下:
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
Run Code Online (Sandbox Code Playgroud)
ie i改变了两次:一次是i++表达,一次是+=声明.
但+=声明的操作数是
i的评价之前i++(的左手侧+=)和i的评价之前i++(的右侧+=).Jon*_*ong 36
首先,i++返回0.然后i增加1.最后i设置为初始值为i0加上i++返回的值,也是零.0 + 0 = 0.
Kaz*_*Kaz 32
这只是抽象语法树的从左到右,自下而上的评估.从概念上讲,表达式的树是从上到下走的,但随着递归从底部弹回树,评估展开.
// source code
i += i++;
// abstract syntax tree
+=
/ \
i ++ (post)
\
i
Run Code Online (Sandbox Code Playgroud)
评估从考虑根节点开始+=.这是表达的主要组成部分.+=必须对左操作数进行求值以确定存储变量的位置,并获得前一个值为零.接下来,必须评估右侧.
右侧是后递增++运算符.它有一个操作数,i既可以作为值的来源进行评估,也可以作为存储值的地方进行评估.操作员评估i,查找0并随后存储1到该位置.它根据返回先前值0的语义返回先前值.
现在控制权回到了+=运营商身上.它现在拥有完成其操作的所有信息.它知道存储结果的位置(存储位置i)以及先前值,并且它具有要添加到先前值的值,即0.所以,i最终得零.
与Java一样,C#通过修复评估顺序来清理C语言的一个非常狡猾的方面.从左到右,自下而上:编码员可能预期的最明显的顺序.
Ash*_*nko 17
后增量方法看起来像这样
int ++(ref int i)
{
int c = i;
i = i + 1;
return c;
}
Run Code Online (Sandbox Code Playgroud)
所以基本上当你调用时i++,i是递增但是在你的情况下返回原始值,它返回0.
Car*_*arl 12
i ++表示:返回i的值然后递增它.
i + = i ++表示:取i的当前值.添加i ++的结果.
现在,让我们添加i = 0作为起始条件.i + = i ++现在评估如下:
注意:在步骤2结束时,i的值实际为1.但是,在步骤3中,通过在i递增之前加载i的值来丢弃它.
与i ++相反,++ i返回递增的值.
因此,i + = ++我会给你1.
Pra*_*man 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;
Run Code Online (Sandbox Code Playgroud)
Adi*_*dil 11
后固定增量运算符,++为变量赋予表达式中的值,然后再将指定i的增量返回零(0)值再次覆盖增加的值(1),这样就得零.您可以在++ Operator(MSDN)中阅读有关增量运算符的更多信息.
++ postfix i在递增之前进行求值,并且+=只计算i一次.
因此,0 + 0 = 0,正如i在递增之前评估和使用的那样,使用后缀格式++.i要先获得增量,请使用前缀form(++i).
(另外,只需注意:你应该得到1,因为0 +(0 + 1)= 1)
参考文献:http://msdn.microsoft.com/en-us/library/sa7629ew.aspx(+ =)
http://msdn.microsoft.com/en-us/library/36x43w8w.aspx(++)
我还预计价值为1 ......但对此问题的一些探索确实澄清了一些观点.
Cosider有以下几种方法:
static int SetSum(ref int a, int b) { return a += b; }
static int Inc(ref int a) { return a++; }
Run Code Online (Sandbox Code Playgroud)
我希望和我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
Run Code Online (Sandbox Code Playgroud)
但后来我得出了另一个结论...... 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; }
Run Code Online (Sandbox Code Playgroud)
调用之后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
Run Code Online (Sandbox Code Playgroud)
这不仅解释了C#正在做什么......而且还解释了为什么很多人都对它感到困惑......包括我在内.
我总是记得一个很好的助记符,如下:
如果++看台后的表达,它返回它的值之前.所以下面的代码
int a = 1;
int b = a++;
Run Code Online (Sandbox Code Playgroud)
是1,因为在它被站立之后增加了a1 .人们称这个帖子修复符号.还有一个预修复符号,其中的事情是完全相反的:如果支架之前,表达式返回它的值后的操作:++ a++
int a = 1;
int b = ++a;
Run Code Online (Sandbox Code Playgroud)
b 在这里是两个.
所以对于你的代码,这意味着
int i = 0;
i += (i++);
Run Code Online (Sandbox Code Playgroud)
i++返回0(如上所述),所以0 + 0 = 0.
i += (++i); // Here 'i' would become two
Run Code Online (Sandbox Code Playgroud)
Scott Meyers在"Effective C++ programming"中描述了这两个符号之间的区别.在内部,i++(postfix)记住值为i,并调用prefix-notation(++i)并返回旧值,i.这就是为什么你应该使用百达++i在for环(虽然我觉得所有的现代编译器转换i++到++i中for环).
你的问题的唯一答案是正确的:因为它是未定义的.
好的,在你们烧我之前..
你们都回答了为什么结果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?
同时t=i++并t=i有不同的结果i.
后期操作是在语句评估之后应该发生的事情.
因此:
int i=0;
i+=i++;
Run Code Online (Sandbox Code Playgroud)
如果我们写的话应该是一样的:
int i=0;
i = i + i ++;
Run Code Online (Sandbox Code Playgroud)
因此同样如下:
int i=0;
i= i + i;
i ++;
Run Code Online (Sandbox Code Playgroud)
因此同样如下:
int i=0;
i = i + i;
i = i + 1;
Run Code Online (Sandbox Code Playgroud)
1如果我们采用理性思考,任何结果都不表示编译器中的错误或语言设计中的错误 - 但MSDN和许多其他来源告诉我们"嘿 - 这是未定义的!"
现在,在我继续之前,即使是我给出的这组例子也没有得到任何人的支持或承认.但是,根据直觉和理性的方式,这应该是结果.
编码人员应该不知道如何编写或翻译程序集!
如果它是以不尊重语言定义的方式编写的 - 这是一个错误!
最后,我从维基百科,递增和递减运算符复制了它:
由于递增/递减运算符修改了它的操作数,因此在同一表达式中多次使用这样的操作数会产生未定义的结果.例如,在诸如x - ++ x之类的表达式中,不清楚应该以什么顺序执行减法和增量运算符.当编译器应用优化时,这样的情况会变得更糟,这可能导致操作的执行顺序与程序员的意图不同.
因此.
正确答案是不应该使用这个!(因为它没有定义!)
是的.. - 即使C#编译器试图以某种方式对其进行标准化,它也具有不可预测的结果.
我没有找到任何C#文档来描述你们所记录的行为,这些行为都是语言的正常或定义明确的行为.我找到的恰恰相反!
[ 从Postfix增量和减量运算符的MSDN文档中复制:++和 - ]
将后缀运算符应用于函数参数时,不保证参数的值在传递给函数之前递增或递减.有关更多信息,请参阅C++标准中的1.9.17节.
注意那些不保证的词......
如果那个答案看起来很傲慢,请原谅我 - 我不是一个傲慢的人.我只想到成千上万的人来这里学习,我读到的答案会误导他们,并会损害他们对这个主题的逻辑和理解.
| 归档时间: |
|
| 查看次数: |
28465 次 |
| 最近记录: |