如果变量x
值最初为0,则表达式x += x += 1
将在C中计算为2,在Javascript中计算为1。
C的语义对我来说很明显: x += x += 1
被解释为x += (x += 1)
,这反过来相当于
x += 1 x += x // where x is 1 at this point
Javascript解释背后的逻辑是什么? 什么规范强制执行这种行为? (顺便说一下,应该注意Java在这里与Javascript一致)。
更新:事实certificate表达式x += x += 1
具有根据C标准的未定义行为(感谢ouah , John Bode , DarkDust , Drew Dormann ),这似乎破坏了一些读者的问题的全部要点。 通过在其中插入标识函数,可以使表达式符合标准: x += id(x += 1)
。 可以对Javascript代码进行相同的修改,问题仍然如所述。 假设大多数读者都能理解“非标准兼容”制定背后的观点,我会保留它,因为它更简洁。
更新2:事实certificate,根据C99,身份函数的引入可能无法解决模糊性。 在这种情况下,亲爱的读者,请将原始问题视为与C ++而不是C99有关,其中“+ =”现在可能最安全地被视为具有唯一定义的操作序列的可重载运算符。 也就是说, x += x += 1
现在等于operator+=(x, operator+=(x, 1))
。 对于通向标准的漫长道路感到抱歉。
JavaScript和Java对此表达式具有非常严格的从左到右的评估规则。 C不会(即使在您提供的具有身份function干预的版本中)。
我有ECMAScript规范(第3版,我承认它已经很老了 – 目前的版本可以在这里找到: http : //www.ecma-international.org/publications/files/ECMA-ST/Ecma-262。 pdf )表示复合赋值运算符的计算如下:
11.13.2复合赋值(op =)
生产AssignmentExpression:LeftHandSideExpression @ = AssignmentExpression,其中@代表上面指出的一个运算符,评估如下:
您注意到Java具有与JavaScript相同的行为 – 我认为其规范更具可读性,因此我将在此处发布一些代码段( html#15.7 ):
15.7评估订单
Java编程语言保证运算符的操作数似乎以特定的评估顺序进行评估,即从左到右。
建议代码不要严格依赖于此规范。 当每个表达式最多包含一个副作用时,代码通常更清晰,作为其最外层的操作,并且当代码不依赖于由于从左到右的表达式评估而出现的确切exception时。
15.7.1首先评估左侧操作数在评估右侧操作数的任何部分之前,似乎完全评估了二元运算符的左侧操作数。 例如,如果左侧操作数包含对变量的赋值,而右侧操作数包含对该同一变量的引用,则引用生成的值将反映赋值首先发生的事实。
…
如果运算符是复合赋值运算符(第15.26.2节),那么对左侧操作数的计算包括记住左侧操作数表示的变量并获取并保存该变量的值以用于隐含的组合操作。
另一方面,在提供中间身份函数的not-undefined-behavior示例中:
x += id(x += 1);
虽然它不是未定义的行为(因为函数调用提供了一个序列点),但是在函数调用之前或之后是否评估最左边的x
仍然是未指定的行为。 因此,虽然它不是’任何事情’未定义的行为,但仍然允许C编译器在调用id()
函数之前评估两个x
变量,在这种情况下,存储到变量的最终值将为1
:
例如,如果x == 0
开始,评估可能如下所示:
tmp = x; // tmp == 0 x = tmp + id( x = tmp + 1) // x == 1 at this point
或者它可以这样评价:
tmp = id( x = x + 1); // tmp == 1, x == 1 x = x + tmp; // x == 2 at this point
请注意,未指定的行为与未定义的行为略有不同,但它仍然不是理想的行为。
x += x += 1;
是C中未定义的行为。
表达式语句违反了序列点规则。
(C99,6.5p2)“在前一个和下一个序列点之间,一个对象的存储值最多只能通过表达式的评估来修改一次。”
在C中, x += x += 1
是未定义的行为 。
您不能指望任何一致的结果,因为尝试在序列点之间两次更新同一对象是未定义的。
至少在C中,这是未定义的行为。 表达式x += x+= 1;
有两个序列点 :在表达式开始之前的一个隐式点 (即:前一个序列点),然后再在;
。 在这两个序列点之间x
被修改两次,这明确地表示为C99标准的未定义行为。 编译器可以自由地做任何它喜欢的事情,包括让守护进程从你的鼻子里飞出来。 如果你很幸运,它只是做你期望的,但根本没有保证。
这与x = x++ + x++;
原因相同x = x++ + x++;
另请参阅C-FAQ以获取更多示例和对此的解释或StackOverflow C ++ FAQ条目未定义的行为和序列点 (AFAIK的C ++规则与C相同)。
这里有几个问题。
首先也是最重要的是C语言规范的这一部分:
6.5表达式
…
2在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次 。 72)此外,先前的值应该是只读的,以确定要存储的值。 73)
…
72)浮点状态标志不是对象,可以在表达式中多次设置。73)此段落呈现未定义的语句表达式,如
i = ++ i + 1; a [i ++] = i;同时允许
i = i + 1; a [i] = i;
强调我的。
表达式x += 1
修改x
(副作用)。 表达式x += x += 1
修改x
两次没有插入序列点,并且它不是只读取先前值来确定要存储的新值; 因此,行为是不确定的(意味着任何结果都同样正确)。 现在,为什么在地球上会出现这个问题? 毕竟, +=
是右关联的,一切都是从左到右评估的,对吧?
错误。
3语法指示运算符和操作数的分组。 74)除了后面指定的(对于函数调用
()
,&&
,||
,?:
和逗号运算符), 子表达式的评估顺序和副作用发生的顺序都是未指定的 。
…
74)语法指定运算符在表达式求值中的优先级,这与本子条款的主要子条款的顺序相同,首先是最高优先级。 因此,例如,允许作为二元+
运算符(6.5.6)的操作数的表达式是在6.5.1到6.5.6中定义的那些表达式。 例外是强制转换表达式(6.5.4)作为一元运算符的操作数(6.5.3),以及下列任何运算符对之间包含的操作数:分组括号()
(6.5.1),下标括号[]
(6.5 .2.1),函数调用括号()
(6.5.2.2)和条件运算符?:
6.5.15)。
强调我的。
通常,优先级和关联性不会影响评估顺序或应用副作用的顺序。 这是一个可能的评估顺序:
t0 = x + 1 t1 = x + t0 x = t1 x = t0
哎呀。 不是我们想要的。
现在,其他语言,如Java和C#(我假设是Javascript) 确实指定操作数总是从左到右进行评估,因此总是有一个明确定义的评估顺序。
从左到右评估所有JavaScript表达式。
……的相关性
var x = 0; x += x += 1
将会…
var x = 0; x = (x + (x = (x + 1)))
因此,由于其从左到右的评估, x
的当前值将在任何其他操作发生之前进行评估。
结果可以这样看……
var x = 0; x = (0 + (x = (0 + 1)))
……这显然等于1
。
所以…
需要了解更多c/c++开发分享为什么(x + = x + = 1)在C和Javascript中的评价方式不同?,也可以关注C/ C++技术分享栏目—计算机技术网(www.ctvol.com)!
var x = 0; x = (x + (x = (x + 1))); // x = (0 + (x = (0 + 1))); // 1 x = (x + (x = (x + 1))); // x = (1 + (x = (1 + 1))); // 3 x = (x + (x = (x + 1))); // x = (3 + (x = (3 + 1))); // 7
以上就是c/c++开发分享为什么(x + = x + = 1)在C和Javascript中的评价方式不同?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/980462.html