操作precedence VS计算顺序 [英] Operator Precedence vs Order of Evaluation

查看:123
本文介绍了操作precedence VS计算顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

2,这些都在规划高度常用的术语和极为重要的一个程序员就知道了。而据我理解这些概念2的紧密结合,可以在没有得到另一谈论前pressions时做的。

让我们举一个简单的例子:

  int类型的= 1; // 1号线
A = A + + + + A; // 2号线
的printf(%d个,一); // 3号线

现在,显而易见的是, 2号线导致未定义行为,因为 C和C序列点++ 包括:


  

      
  1. 左侧的评估和的和放权数之间;&安培; (逻辑
      AND)|| (逻辑OR)和逗号
      运营商。例如,在
      前pression * P ++ = 0&安培;!&安培; * Q ++!= 0时,所有
      子前pression副作用
      * P ++!= 0任何企图访问q之前完成。


  2.   
  3. 三元的第一个操作的评价之间
      问号运营商和
      第二个或第三个操作数。例如,
      在EX pression A =(* P ++)? (* P ++)
      :0有一个序列点之后
      第一个* P +,这意味着它已经
      被增加的时间
      二审被执行。


  4.   
  5. 目前完整前pression的结束。此类别包括前pression
      语句(如分配
      A = B),return语句时,
      如果控制,开关的前pressions,
      同时,还是做-whil​​e语句,所有
      在三恩pressions for语句。


  6.   
  7. 在函数调用输入函数之前。的顺序
      评估参数不
      指定,但该序列点
      也就是说,他们所有的副作用
      完成之前的功能
      输入。在EX pression F(我+ +)+
      克(J ++)+ H(K +),F是带一个
      i的初始值的参数,
      但我在进入前递增
      身体的F。同样,j和k是
      进入G和H前更新
      分别。然而,这是不
      以何种顺序f()的,克()中,h(指定)
      被执行时,也不能在其中顺序I,J,
      K的递增。 j的值,并
      K的f的体,因此
      未定义。 3 注意函数
      调用F(A,B,C)是不是使用的
      逗号运算和秩序
      评价为A,B,和C是
      不确定的。


  8.   
  9. 在函数返回后,返回值被复制到
      调用上下文。 (这个顺序点
      仅在C ++标准规定;
      这是present只是含蓄地在
      以下。)


  10.   
  11. 在一个初始化的结束;例如,经过5评价
      在声明INT A = 5;


  12.   

因此​​,通过点#3将:

<青霉>在一个完整的前pression的结束。此类别包括前pression语句(如赋值A = B),return语句,如果,开关控制前pressions,同时,还是做-whil​​e语句,并且所有三个前$ P $在pssions for语句。的

2号线明确导致未定义的行为。这显示了如何未定义行为是紧密结合的序列点

现在,让我们再举一个例子:

  INT X = 10,Y = 1,Z = 2; // 4号线
INT结果= X&LT; Y&LT; Z; // 5号线

现在它显然 5号线将变量结果商店 1

现在除权pression X&LT; Y&LT; Z 在 5号线可以评价为两种:

X&LT;(Y&LT; Z)或(X&LT; Y)LT; Z 。在第一种情况下结果值 0 而在第二种情况下的结果 1 。但我们知道,当操作precedence 相等/相同的 - 关联性进场,因此,作为(X&LT; Y)LT;ž

这是什么在这个说MSDN文章

的precedence和C运营商的关联性影响的前pressions操作数的分组和评估。运营商的precedence有意义只有当其他运营商较高或较低的precedence是present。集成到更precedence运营商前pressions首先评估。 precedence也可以由字描述绑定。具有较高precedence运营据说有更紧密的结合。

现在,对上述文章:

它提到了集成到更precedence运营商首先评估前pressions。

这听起来可能不正确。但是,我认为文章是不是说什么错的,如果我们考虑到()也是运营商 X&LT; Y&LT; Z 是一样的(X&LT; Y)LT; Z 。我的理由是,如果相关性不发挥作用,那么因为&LT完整的前pressions评估将变得模糊; 不是的Sequence点

此外,另一个链接我发现说,这在操作precedence和结合的:

此页面列出C运算符在precedence的顺序(最高到最低)。它们的结合表明应用在离pression为了什么平等的运营商precedence的。

因此​​,采取 INT结果的第二个例子= X&LT; Y&LT; Z ,我们可以看到这里有在所有3个前pressions, X 以Z ,因为,一个最简单的形式前pression由一个常量或对象。因此,前pressions结果 X 以Z 将在那里的右值,即 10 1 2 分别。因此,现在我们可以跨preT X&LT; Y&LT; Z 为 10。1 2

现在,没有关联性发挥作用,因为现在我们已经2前pressions进行评估,无论是 10。1 1下; 2 并且由于操作者的precedence是一样的,它们从左至右评价

有鉴于此最后一个例子是我的观点:

  INT =设为myVal(的printf(操作\\ n),输出(precedence \\ n)的printf(与\\ n),
的printf(评估的\\ n阶));

现在在上面的例子中,因为逗号经营者有相同的precedence,前pressions评估从左右汉字最后 printf()函数的返回值存储在设为myVal

SO / IEC 9899:下201X J.1不确定的行为它提到:

在SUBEX pressions的计算顺序和顺序副作用
发生,除了作为函数调用(),放大器指定;&放;, ||,:和逗号?
运营商(6.5)。

现在我想知道,这将是错误的说法:

评估顺序取决于运营商的precedence,留下不确定的行为的案例。

我想如果有错误是在东西我在我的问题说,作出纠正。
我张贴了这个问题的原因是因为在MSDN文章在我心中引起的混淆。它是在错误或不?


解决方案

是,MSDN文章是错误的,至少相对于标准C和C ++ 1

现在,以评估顺序被precedence决定的,没有它不是!就这么简单。只是例子,让我们考虑你的例X&LT; Y&LT; Z 。根据该关联规则,这解析为(X&LT; Y)LT; Z 。现在,考虑一个堆栈机上评价这一前pression。这是完全允许的为它做这样的事情:

 推(Z); //计算它的参数和在压入堆栈值
 推(Y);
 推(X);
 test_less(); //比较TOS到TOS(1),推导致堆栈
 test_less();

本评估以Z X ,但仍评估(X&LT; Y),那么比较的结果进行比较,以以Z ,就像它应该

摘要:评估顺序是独立的关联性

precedence是相同的方式。我们可以改变前pression到 X * Y + Z ,依然评估以Z X

 推(Z);
推(Y);
推(X);
穆尔();
加();

摘要:评价秩序是独立precedence的

当/如果我们副作用增加,这仍然是相同的。我觉得想副作用,它的教育由一个单独的执行线程中进行,以加入在下一序列点(如前$的结束p $ pssion)。因此,像 A = B + + + +℃; 就可以执行这样的:

 推(一);
推(B);
推(C + 1);
side_effects_thread.queue(INC,B);
side_effects_thread.queue(INC,C);
加();
分配();
加入(side_effects_thread);

这也说明了为什么明显的相关性并不一定会影响评价的秩序无论是。尽管 A 是赋值的目标,这仍然评估 A 的评价要么 b C 。还要注意的是,虽然我已经写为线之上,这可能也无妨是的游泳池的线程,所有的并行执行的,所以你没有得到有关订单的任何保证一个增量与另一任。

除非硬件直接有(和便宜的),线程安全队列支持,这可能不会在实际的执行中使用(甚至那么它不太可能)。把东西放到一个线程安全的队列中通常会有比做一个增量相当多的开销,所以很难想象有人曾经这样做在现实中。从概念上讲,然而,这个想法是适合标准的要求:当您使用pre /后递增/递减操作,你指定的操作,将除权pression的那部分后的某个时间发生评估,并且将在下一序列点完整。

编辑:虽然它不完全线程,有些结构也允许这样的并行执行。对于一些例子,英特尔安腾和VLIW处理器,例如一些的DSP,允许编译器指定在并行执行若干指令。多数VLIW机器具有特定的指令包大小限制的并行执行的指令的数目。安腾还使用的指令报文,但表示一个位在一个指令包地说,在当前包中的指令可以并行与那些在下一分组被执行。使用机制,这样,你得到的指令并行执行,就像如果你使用的架构与我们大多数人都比较熟悉多线程。

摘要:评价顺序是独立的明显依赖

在下一序列点之前使用值的任何尝试都给出了明确的行为 - 尤其是其他线程是(可能)在这段时间修改数据,你必须的没有的方式的同步与其他线程访问。在使用它的任何尝试都将导致不确定的行为。

只是一个(当然,现在比较牵强)的例子,认为你的code运行64位虚拟机上,但真正的硬件是一个8位处理器。时递增一个64位的变量,它执行一个序列是这样的:

 负载变量[0]
增量
存储变量[0]
的for(int i = 1; I&LT; 8;我++){
    负载变量[I]
    add_with_carry 0
    店内变量[I]
}

如果您在序列的中间某处读取的值,你可以得到的东西,只有一些修改的字节,所以你得到的是既不是旧值的的新的。

这确切的例子可能是pretty牵强,但一个不那么极端的版本(例如,在32位机器上的64位变量)实际上是相当普遍的。

结论

评估顺序做的不可以依赖于precedence,关联性,或(一定)上明显的相关性。尝试使用一个变量其中pre /后递增/递减已在离pression的任何其他部分应用确实给予的完全的未定义的行为。虽然实际的崩溃是不太可能的,你是绝对的的保证得到无论是旧值还是新的 - 你可以得到什么东西完全


1 我没有检查这个特殊的文章,但相当多的MSDN文章谈论微软的托管C ++和/或C ++ / CLI但很少或没有指出的是,他们不适用于标准C或C ++。这可以给他们声称他们已经决定适用于他们自己的语言的规则实际上也适用于标准语言的假象。在这些情况下,物品并非技术上假 - 他们根本没有任何与标准C或C ++。如果您尝试应用这些语句标准C或C ++,结果是假的。

These 2 are highly commonly used terms in programming and extremely important for a programmer to know. And as far as i understand these 2 concepts are tightly bound, one cannot do without the other when talking about expressions.

Let us take a simple example:

int a=1;  // Line 1
a = a++ + ++a;  // Line 2
printf("%d",a);  // Line 3

Now, it is evident that Line 2 leads to Undefined Behavior, since Sequence points in C and C++ include:

  1. Between evaluation of the left and right operands of the && (logical AND), || (logical OR), and comma operators. For example, in the expression *p++ != 0 && *q++ != 0, all side effects of the sub-expression *p++ != 0 are completed before any attempt to access q.

  2. Between the evaluation of the first operand of the ternary "question-mark" operator and the second or third operand. For example, in the expression a = (*p++) ? (*p++) : 0 there is a sequence point after the first *p++, meaning it has already been incremented by the time the second instance is executed.

  3. At the end of a full expression. This category includes expression statements (such as the assignment a=b;), return statements, the controlling expressions of if, switch, while, or do-while statements, and all three expressions in a for statement.

  4. Before a function is entered in a function call. The order in which the arguments are evaluated is not specified, but this sequence point means that all of their side effects are complete before the function is entered. In the expression f(i++) + g(j++) + h(k++), f is called with a parameter of the original value of i, but i is incremented before entering the body of f. Similarly, j and k are updated before entering g and h respectively. However, it is not specified in which order f(), g(), h() are executed, nor in which order i, j, k are incremented. The values of j and k in the body of f are therefore undefined.3 Note that a function call f(a,b,c) is not a use of the comma operator and the order of evaluation for a, b, and c is unspecified.

  5. At a function return, after the return value is copied into the calling context. (This sequence point is only specified in the C++ standard; it is present only implicitly in C.)

  6. At the end of an initializer; for example, after the evaluation of 5 in the declaration int a = 5;.

Thus, going by Point # 3:

At the end of a full expression. This category includes expression statements (such as the assignment a=b;), return statements, the controlling expressions of if, switch, while, or do-while statements, and all three expressions in a for statement.

Line 2 clearly leads to Undefined Behavior. This shows how Undefined Behaviour is tightly coupled with Sequence Points.

Now let us take another example:

int x=10,y=1,z=2; // Line 4
int result = x<y<z; // Line 5

Now its evident that Line 5 will make the variable result store 1.

Now the expression x<y<z in Line 5 can be evaluated as either:

x<(y<z) or (x<y)<z. In the first case the value of result will be 0 and in the second case result will be 1. But we know, when the Operator Precedence is Equal/Same - Associativity comes into play, hence, is evaluated as (x<y)<z.

This is what is said in this MSDN Article:

The precedence and associativity of C operators affect the grouping and evaluation of operands in expressions. An operator's precedence is meaningful only if other operators with higher or lower precedence are present. Expressions with higher-precedence operators are evaluated first. Precedence can also be described by the word "binding." Operators with a higher precedence are said to have tighter binding.

Now, about the above article:

It mentions "Expressions with higher-precedence operators are evaluated first."

It may sound incorrect. But, i think the article is not saying something wrong if we consider that () is also an operator x<y<z is same as (x<y)<z. My reasoning is if associativity does not come into play, then the complete expressions evaluation would become ambiguous since < is not a Sequence Point.

Also, another link i found says this on Operator Precedence and Associativity:

This page lists C operators in order of precedence (highest to lowest). Their associativity indicates in what order operators of equal precedence in an expression are applied.

So taking, the second example of int result=x<y<z, we can see here that there are in all 3 expressions, x, y and z, since, the simplest form of an expression consists of a single literal constant or object. Hence the result of the expressions x, y, z would be there rvalues, i.e., 10, 1 and 2 respectively. Hence, now we may interpret x<y<z as 10<1<2.

Now, doesn't Associativity come into play since now we have 2 expressions to be evaluated, either 10<1 or 1<2 and since the precedence of operator is same, they are evaluated from left to right?

Taking this last example as my argument:

int myval = ( printf("Operator\n"), printf("Precedence\n"), printf("vs\n"),
printf("Order of Evaluation\n") );

Now in the above example, since the comma operator has same precedence, the expressions are evaluated left-to-right and the return value of the last printf() is stored in myval.

In SO/IEC 9899:201x under J.1 Unspecified behavior it mentions:

The order in which subexpressions are evaluated and the order in which side effects take place, except as specified for the function-call (), &&, ||, ?:, and comma operators (6.5).

Now i would like to know, would it be wrong to say:

Order of Evaluation depends on the precedence of operators, leaving cases of Unspecified Behavior.

I would like to be corrected if any mistakes were made in something i said in my question. The reason i posted this question is because of the confusion created in my mind by the MSDN Article. Is it in Error or not?

解决方案

Yes, the MSDN article is in error, at least with respect to standard C and C++1.

Now, as to evaluation order being determined by precedence, no it's not! It's as simple as that. Just for example, let's consider your example of x<y<z. According to the associativity rules, this parses as (x<y)<z. Now, consider evaluating this expression on a stack machine. It's perfectly allowable for it to do something like this:

 push(z);    // Evaluates its argument and pushes value on stack
 push(y);
 push(x);
 test_less();  // compares TOS to TOS(1), pushes result on stack
 test_less();

This evaluates z before x or y, but still evaluates (x<y), then compares the result of that comparison to z, just as it's supposed to.

Summary: Order of evaluation is independent of associativity.

Precedence is the same way. We can change the expression to x*y+z, and still evaluate z before x or y:

push(z);
push(y);
push(x);
mul();
add();

Summary: Order of evaluation is independent of precedence.

When/if we add in side effects, this remains the same. I think it's educational to think of side effects as being carried out by a separate thread of execution, with a join at the next sequence point (e.g., the end of the expression). So something like a=b++ + ++c; could be executed something like this:

push(a);
push(b);
push(c+1);
side_effects_thread.queue(inc, b);
side_effects_thread.queue(inc, c);
add();
assign();
join(side_effects_thread);

This also shows why an apparent dependency doesn't necessarily affect order of evaluation either. Even though a is the target of the assignment, this still evaluates a before evaluating either b or c. Also note that although I've written it as "thread" above, this could also just as well be a pool of threads, all executing in parallel, so you don't get any guarantee about the order of one increment versus another either.

Unless the hardware had direct (and cheap) support for thread-safe queuing, this probably wouldn't be used in in a real implementation (and even then it's not very likely). Putting something into a thread-safe queue will normally have quite a bit more overhead than doing a single increment, so it's hard to imagine anybody ever doing this in reality. Conceptually, however, the idea is fits the requirements of the standard: when you use a pre/post increment/decrement operation, you're specifying an operation that will happen sometime after that part of the expression is evaluated, and will be complete at the next sequence point.

Edit: though it's not exactly threading, some architectures do allow such parallel execution. For a couple of examples, the Intel Itanium and VLIW processors such as some DSPs, allow a compiler to designate a number of instructions to be executed in parallel. Most VLIW machines have a specific instruction "packet" size that limits the number of instructions executed in parallel. The Itanium also uses packets of instructions, but designates a bit in an instruction packet to say that the instructions in the current packet can be executed in parallel with those in the next packet. Using mechanisms like this, you get instructions executing in parallel, just like if you used multiple threads on architectures with which most of us are more familiar.

Summary: Order of evaluation is independent of apparent dependencies

Any attempt at using the value before the next sequence point gives undefined behavior -- in particular, the "other thread" is (potentially) modifying that data during that time, and you have no way of synchronizing access with the other thread. Any attempt at using it leads to undefined behavior.

Just for a (admittedly, now rather far-fetched) example, think of your code running on a 64-bit virtual machine, but the real hardware is an 8-bit processor. When you increment a 64-bit variable, it executes a sequence something like:

load variable[0]
increment
store variable[0]
for (int i=1; i<8; i++) {
    load variable[i]
    add_with_carry 0
    store variable[i]
}

If you read the value somewhere in the middle of that sequence, you could get something with only some of the bytes modified, so what you get is neither the old value nor the new one.

This exact example may be pretty far-fetched, but a less extreme version (e.g., a 64-bit variable on a 32-bit machine) is actually fairly common.

Conclusion

Order of evaluation does not depend on precedence, associativity, or (necessarily) on apparent dependencies. Attempting to use a variable to which a pre/post increment/decrement has been applied in any other part of an expression really does give completely undefined behavior. While an actual crash is unlikely, you're definitely not guaranteed to get either the old value or the new one -- you could get something else entirely.


1 I haven't checked this particular article, but quite a few MSDN articles talk about Microsoft's Managed C++ and/or C++/CLI but do little or nothing to point out that they don't apply to standard C or C++. This can give the false appearance that they're claiming the rules they have decided to apply to their own languages actually apply to the standard languages. In these cases, the articles aren't technically false -- they just don't have anything to do with standard C or C++. If you attempt to apply those statements to standard C or C++, the result is false.

这篇关于操作precedence VS计算顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆