将递增/递减运算符放在三元/条件运算符中安全吗?

时间:2022-01-03 09:09:41

Here's an example

这里有一个例子

#include <iostream>
using namespace std;
int main()
{   
    int x = 0;
    cout << (x == 0 ? x++ : x) << endl; //operator in branch
    cout << "x=" << x << endl;
    cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
    cout << "x=" << x << endl;
    return 0;
}

output:

输出:

0
x=1
1
x=1

I understand the output, but is this undefined behaviour or not? Is the order of evaluation guaranteed in either case?

我理解输出,但这是未定义的行为吗?两种情况下的评估顺序都有保证吗?

Even if guaranteed, I'm quite aware using increment/decrement can quickly become an issue for readability. I only ask as I saw similar code and was immediately unsure, given there are lots of examples of ambiguous/undefined use of increment/decrement operators, such as...

即使有保证,我也很清楚使用递增/递减会很快成为可读性的问题。当我看到类似的代码时,我只问了这个问题,但我马上就不确定了,因为有很多使用递增/递减运算符的例子,比如……

  • C++ does not define the order in which function parameters are evaluated.

    c++没有定义函数参数求值的顺序。↪

    int nValue = Add(x, ++x);
    
  • The C++ language says you cannot modify a variable more than once between sequence points.

    c++语言说,在序列点之间不能修改一个变量超过一次。↪

     x = ++y + y++
    
  • Because increment and decrement operators have side effects, using expressions with increment or decrement operators in a preprocessor macro can have undesirable results.

    因为递增和递减运算符有副作用,所以在预处理器宏中使用带递增或递减运算符的表达式会产生不希望的结果。↪

     #define max(a,b) ((a)<(b))?(b):(a)
     k = max( ++i, j );
    

5 个解决方案

#1


33  

For the conditional operator (§5.16 [expr.cond]/p1):

条件操作符(§5.16[expr.cond]/ p1):

Every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second or third expression.

与第一个表达式相关的每个值计算和副作用都在第二个或第三个表达式相关的每个值计算和副作用之前进行排序。

For the logical OR operator (§5.15 [expr.log.or]/p1-2):

逻辑或操作符(§5.15[expr.log.or]/ p1-2):

the second operand is not evaluated if the first operand evaluates to true. [...] If the second expression is evaluated, every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second expression.

如果第一个操作数计算为true,则不计算第二个操作数。[…如果计算第二个表达式,那么与第一个表达式相关的每个值计算和副作用都会在每个值计算和与第二个表达式相关的副作用之前进行排序。

The behavior of your code is well-defined.

代码的行为是定义良好的。

#2


10  

There is a guaranteed order of execution in ternary operators and boolean && and || operations, so there is no conflict in evaluation sequence points.

在三元运算符和布尔&&和||运算中有保证的执行顺序,因此在计算序列点中没有冲突。

One at a time

一次一个

 cout << (x == 0 ? x++ : x) << endl; //operator in branch

Will always output x but will increment it only if it was 0.

总是输出x,但只有当x为0时才增加。

 cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

This is well defined too, if x was 1 it will not evaluate the RHS, if it wasn't it will decrement it but --x will never be 0, so it will be true iff x==1, in which case x will also now be 0.

这个定义也很好,如果x= 1它就不会对RHS求值,如果不是,它就会递减但是,x永远不会是0,所以它是正确的iff x= 1,在这种情况下,x现在也是0。

In the latter case if x is INT_MIN it is not well-defined behaviour to decrement it (and it would execute).

在后一种情况下,如果x是INT_MIN,那么它没有定义良好的行为来减少它(它将执行)。

That can't happen in the first case where x won't be 0 if it is INT_MAX so you are safe.

这在第一个情况下是不会发生的,如果是INT_MAX, x就不会是0,所以你是安全的。

#3


7  

I understand the output, but is this undefined behaviour or not?

我理解输出,但这是未定义的行为吗?

Code is perfectly defined. C11 standard says:

代码是完全定义。C11标准说:

6.5.15 Conditional operator

The first operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated). The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.110)

计算第一个操作数;它的求值和第二个或第三个操作数的求值之间有一个序列点(无论哪个被求值)。第二个操作数只在第一次比较不等于0时进行计算;只有当第一个操作数比较为0时,才计算第三个操作数;结果是第二个或第三个操作数的值(无论哪个值),转换为下面描述的类型。110)

6.5.14 Logical OR operator

Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares unequal to 0, the second operand is not evaluated.

不像位|算子,||算子保证从左到右的取值;如果第二个操作数被求值,在第一个操作数和第二个操作数的求值之间有一个序列点。如果第一个操作数不等于0,则不计算第二个操作数。

Further wiki explains it with example:

进一步的wiki解释了它的例子:

  • Between evaluation of the left and right operands of the && (logical AND), || (logical OR) (as part of short-circuit evaluation), 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.

    在对&&(逻辑与)、||(逻辑或)(作为短路评估的一部分)和逗号运算符之间的左和右操作数之间的评估。例如,在表达式*p+ != 0 && *q+ != 0中,子表达式*p++ != 0的所有副作用都在试图访问q之前完成。

  • 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.

    在第一个操作数和第二次或第三个操作数之间的第一个操作数之间的评价。例如,在表达式a = (*p++)中?(*p++): 0在第一个*p+后面有一个序列点,这意味着在执行第二个实例时,它已经增加了。

The rule for || and ?: is same for C++ (section 5.15 and 5.16) as in C.

||和?:的规则与C中的规则相同(第5.15和5.16节)。


Is the order of evaluation guaranteed in either case?

两种情况下的评估顺序都有保证吗?

Yes. The order of evaluation of the operands of operators ||, &&, , and ?: is guaranteed to be from left to right.

是的。||、&、和?:运算符的运算数的取值顺序保证是从左到右。

#4


3  

In C an object's stored value can be modified only once between two sequence points.

在C中,对象的存储值只能在两个序列点之间修改一次。

A sequence point occurs:

一个序列点发生:

  1. At the end of full expression.
  2. 在完整表达的结尾。
  3. At the &&, || and ?: operators
  4. 在||和?:运营商
  5. At a function call.
  6. 在一个函数调用。

So for example this expression x = i++ * i++ is undefined, whereas x = i++ && i++ is perfectly legal.

例如,这个表达式x = i++ * i++ +是没有定义的,而x = i++ && & ++ ++ +是完全合法的。

Your code shows defined behaviour.

您的代码显示了已定义的行为。

int x=0;

int x = 0;

cout << ( x == 0 ? x++ : x) << endl;

cout << (x == 0)< endl;

In the above expression x is 0, so x++ will be executed, here x++ is post increment so it will output 0.

在上面的表达式中,x = 0,所以x++将被执行,这里x++是后增量,所以输出为0。

cout << "x=" << x << endl;

cout << < x= << << << span

In the above expression as x now has value 1 so the output will be 1.

在上面的表达式中,x的值为1,所以输出为1。

cout << (x == 1 || --x == 0 ? 1 : 2) << endl;

cout << (x == 1 || -x == 0)1: 2) < endl;

Here x is 1 so the next condition is not evaluated(--x == 0) and the output will 1.

这里x是1,所以下一个条件没有求值(x == 0),输出结果是1。

cout << "x=" << x << endl;

cout << < x= << << << span

As the expression --x == 0 is not evaluated the output will again be 1.

当表达式-x == 0时,输出将不再是1。

#5


2  

Yes, it is safe to use the increment/decrement operators as you have. Here is what is happening in your code:

是的,使用递增/递减运算符是安全的。以下是在你的代码中发生的事情:

Snippet #1

cout << (x == 0 ? x++ : x) << endl; //operator in branch

In this snippet, you are testing if x == 0, which is true. Since it is true, your ternary expression evaluates the x++. Since you are using a post-increment here, the original value for x is printed to the standard output stream, and then x is incremented.

在这段代码中,您正在测试x是否= 0,这是正确的。因为它是正确的,所以三元表达式计算x+。由于这里使用的是后增量,所以x的原始值被打印到标准输出流中,然后x被递增。

Snippet #2

cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

This snippet is a bit more confusing, but it still yields a predictable result. At this point, x = 1 from the first snippet. In the ternary expression, the conditional part is evaluated first; however, due to Short-Circuiting, the second condition, --x == 0, is never evaluated.

这段代码有点让人困惑,但它仍然会产生可预测的结果。此时,第一个代码片段中的x = 1。在三元表达式中,条件部分首先求值;但是,由于短路,第二个条件x = 0永远不会被求值。

For C++ the operators || and && are the short-circuiting boolean operators for logical OR and logical AND respectively. When you use these operators, your conditions are checked (from left to right) until the final result can be determined. Once the result is determined, no more conditions are checked.

对于c++,运算符||和&是逻辑运算符或逻辑运算符和逻辑运算符的短路布尔运算符。当您使用这些操作符时,将检查您的条件(从左到右),直到确定最终结果。一旦确定了结果,就不再检查任何条件。

Looking at snippet #2, your first condition checks if x == 1. Since your first condition evaluates to true and you are using the logical OR, there is no need to keep evaluating other conditions. That means that --x == 0 is never executed.

查看代码片段2,您的第一个条件检查x == 1。由于您的第一个条件的计算结果为true,并且您正在使用逻辑OR,因此没有必要继续评估其他条件。这意味着,x == 0永远不会被执行。


A quick side-note about short-circuiting:

Short-circuiting is useful for increasing performance in your program. Suppose you had a condition like this which calls several time-expensive functions:

短路对于提高程序的性能非常有用。假设您有这样一个条件,它调用了几个耗时很长的函数:

if (task1() && task2())
{ 
    //...Do something...
}

In this example, task2 should never be called unless task1 completes successfully (task2 depends on some data that is altered by task1).

在本例中,除非task1成功完成,否则永远不会调用task2 (task2依赖于task1修改的数据)。

Because we are using a short-circuiting AND operator, if task1 fails by returning false, then if-statement has the sufficient information to exit early and stop checking other conditions. This means that task2 is never called.

因为我们使用的是短路和运算符,如果task1通过返回false失败,那么if-statement就有足够的信息提前退出并停止检查其他条件。这意味着task2从未被调用。

#1


33  

For the conditional operator (§5.16 [expr.cond]/p1):

条件操作符(§5.16[expr.cond]/ p1):

Every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second or third expression.

与第一个表达式相关的每个值计算和副作用都在第二个或第三个表达式相关的每个值计算和副作用之前进行排序。

For the logical OR operator (§5.15 [expr.log.or]/p1-2):

逻辑或操作符(§5.15[expr.log.or]/ p1-2):

the second operand is not evaluated if the first operand evaluates to true. [...] If the second expression is evaluated, every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second expression.

如果第一个操作数计算为true,则不计算第二个操作数。[…如果计算第二个表达式,那么与第一个表达式相关的每个值计算和副作用都会在每个值计算和与第二个表达式相关的副作用之前进行排序。

The behavior of your code is well-defined.

代码的行为是定义良好的。

#2


10  

There is a guaranteed order of execution in ternary operators and boolean && and || operations, so there is no conflict in evaluation sequence points.

在三元运算符和布尔&&和||运算中有保证的执行顺序,因此在计算序列点中没有冲突。

One at a time

一次一个

 cout << (x == 0 ? x++ : x) << endl; //operator in branch

Will always output x but will increment it only if it was 0.

总是输出x,但只有当x为0时才增加。

 cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

This is well defined too, if x was 1 it will not evaluate the RHS, if it wasn't it will decrement it but --x will never be 0, so it will be true iff x==1, in which case x will also now be 0.

这个定义也很好,如果x= 1它就不会对RHS求值,如果不是,它就会递减但是,x永远不会是0,所以它是正确的iff x= 1,在这种情况下,x现在也是0。

In the latter case if x is INT_MIN it is not well-defined behaviour to decrement it (and it would execute).

在后一种情况下,如果x是INT_MIN,那么它没有定义良好的行为来减少它(它将执行)。

That can't happen in the first case where x won't be 0 if it is INT_MAX so you are safe.

这在第一个情况下是不会发生的,如果是INT_MAX, x就不会是0,所以你是安全的。

#3


7  

I understand the output, but is this undefined behaviour or not?

我理解输出,但这是未定义的行为吗?

Code is perfectly defined. C11 standard says:

代码是完全定义。C11标准说:

6.5.15 Conditional operator

The first operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated). The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.110)

计算第一个操作数;它的求值和第二个或第三个操作数的求值之间有一个序列点(无论哪个被求值)。第二个操作数只在第一次比较不等于0时进行计算;只有当第一个操作数比较为0时,才计算第三个操作数;结果是第二个或第三个操作数的值(无论哪个值),转换为下面描述的类型。110)

6.5.14 Logical OR operator

Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares unequal to 0, the second operand is not evaluated.

不像位|算子,||算子保证从左到右的取值;如果第二个操作数被求值,在第一个操作数和第二个操作数的求值之间有一个序列点。如果第一个操作数不等于0,则不计算第二个操作数。

Further wiki explains it with example:

进一步的wiki解释了它的例子:

  • Between evaluation of the left and right operands of the && (logical AND), || (logical OR) (as part of short-circuit evaluation), 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.

    在对&&(逻辑与)、||(逻辑或)(作为短路评估的一部分)和逗号运算符之间的左和右操作数之间的评估。例如,在表达式*p+ != 0 && *q+ != 0中,子表达式*p++ != 0的所有副作用都在试图访问q之前完成。

  • 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.

    在第一个操作数和第二次或第三个操作数之间的第一个操作数之间的评价。例如,在表达式a = (*p++)中?(*p++): 0在第一个*p+后面有一个序列点,这意味着在执行第二个实例时,它已经增加了。

The rule for || and ?: is same for C++ (section 5.15 and 5.16) as in C.

||和?:的规则与C中的规则相同(第5.15和5.16节)。


Is the order of evaluation guaranteed in either case?

两种情况下的评估顺序都有保证吗?

Yes. The order of evaluation of the operands of operators ||, &&, , and ?: is guaranteed to be from left to right.

是的。||、&、和?:运算符的运算数的取值顺序保证是从左到右。

#4


3  

In C an object's stored value can be modified only once between two sequence points.

在C中,对象的存储值只能在两个序列点之间修改一次。

A sequence point occurs:

一个序列点发生:

  1. At the end of full expression.
  2. 在完整表达的结尾。
  3. At the &&, || and ?: operators
  4. 在||和?:运营商
  5. At a function call.
  6. 在一个函数调用。

So for example this expression x = i++ * i++ is undefined, whereas x = i++ && i++ is perfectly legal.

例如,这个表达式x = i++ * i++ +是没有定义的,而x = i++ && & ++ ++ +是完全合法的。

Your code shows defined behaviour.

您的代码显示了已定义的行为。

int x=0;

int x = 0;

cout << ( x == 0 ? x++ : x) << endl;

cout << (x == 0)< endl;

In the above expression x is 0, so x++ will be executed, here x++ is post increment so it will output 0.

在上面的表达式中,x = 0,所以x++将被执行,这里x++是后增量,所以输出为0。

cout << "x=" << x << endl;

cout << < x= << << << span

In the above expression as x now has value 1 so the output will be 1.

在上面的表达式中,x的值为1,所以输出为1。

cout << (x == 1 || --x == 0 ? 1 : 2) << endl;

cout << (x == 1 || -x == 0)1: 2) < endl;

Here x is 1 so the next condition is not evaluated(--x == 0) and the output will 1.

这里x是1,所以下一个条件没有求值(x == 0),输出结果是1。

cout << "x=" << x << endl;

cout << < x= << << << span

As the expression --x == 0 is not evaluated the output will again be 1.

当表达式-x == 0时,输出将不再是1。

#5


2  

Yes, it is safe to use the increment/decrement operators as you have. Here is what is happening in your code:

是的,使用递增/递减运算符是安全的。以下是在你的代码中发生的事情:

Snippet #1

cout << (x == 0 ? x++ : x) << endl; //operator in branch

In this snippet, you are testing if x == 0, which is true. Since it is true, your ternary expression evaluates the x++. Since you are using a post-increment here, the original value for x is printed to the standard output stream, and then x is incremented.

在这段代码中,您正在测试x是否= 0,这是正确的。因为它是正确的,所以三元表达式计算x+。由于这里使用的是后增量,所以x的原始值被打印到标准输出流中,然后x被递增。

Snippet #2

cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

This snippet is a bit more confusing, but it still yields a predictable result. At this point, x = 1 from the first snippet. In the ternary expression, the conditional part is evaluated first; however, due to Short-Circuiting, the second condition, --x == 0, is never evaluated.

这段代码有点让人困惑,但它仍然会产生可预测的结果。此时,第一个代码片段中的x = 1。在三元表达式中,条件部分首先求值;但是,由于短路,第二个条件x = 0永远不会被求值。

For C++ the operators || and && are the short-circuiting boolean operators for logical OR and logical AND respectively. When you use these operators, your conditions are checked (from left to right) until the final result can be determined. Once the result is determined, no more conditions are checked.

对于c++,运算符||和&是逻辑运算符或逻辑运算符和逻辑运算符的短路布尔运算符。当您使用这些操作符时,将检查您的条件(从左到右),直到确定最终结果。一旦确定了结果,就不再检查任何条件。

Looking at snippet #2, your first condition checks if x == 1. Since your first condition evaluates to true and you are using the logical OR, there is no need to keep evaluating other conditions. That means that --x == 0 is never executed.

查看代码片段2,您的第一个条件检查x == 1。由于您的第一个条件的计算结果为true,并且您正在使用逻辑OR,因此没有必要继续评估其他条件。这意味着,x == 0永远不会被执行。


A quick side-note about short-circuiting:

Short-circuiting is useful for increasing performance in your program. Suppose you had a condition like this which calls several time-expensive functions:

短路对于提高程序的性能非常有用。假设您有这样一个条件,它调用了几个耗时很长的函数:

if (task1() && task2())
{ 
    //...Do something...
}

In this example, task2 should never be called unless task1 completes successfully (task2 depends on some data that is altered by task1).

在本例中,除非task1成功完成,否则永远不会调用task2 (task2依赖于task1修改的数据)。

Because we are using a short-circuiting AND operator, if task1 fails by returning false, then if-statement has the sufficient information to exit early and stop checking other conditions. This means that task2 is never called.

因为我们使用的是短路和运算符,如果task1通过返回false失败,那么if-statement就有足够的信息提前退出并停止检查其他条件。这意味着task2从未被调用。