【问题标题】:Side effect vs value calculation in GCC ternary conditional operator?GCC三元条件运算符中的副作用与值计算?
【发布时间】:2014-09-15 19:54:21
【问题描述】:


我有以下代码对我产生了意想不到的结果:

 #include < stdio.h >

int a = 0, value;

int main(void)
{
    // Testing the evaluation order of multiple
    // conditional operators:
    value = (a == 3) ? 3 : (a = 3) ? 5 : 0;
    printf("%d\n", value);

    return 0;
}

我期待这段代码打印 3,看到条件运算符评估
从右到左,在 ? 处有一个序列点。最先被处决的人
操作,而它实际上打印 5.
假设存在于两个序列之间的表达式的副作用是错误的吗
当表达式的值是时也会计算点数吗?
如果我添加 printf("%d\n" a);不过我打印了 3 个,所以副作用就完成了。
还是只是控制剂量真正传递给子表达式 which
正式计算“第一”吗?
我宁愿赌后者,因为将 'a' 的值更改为 3 和右值
在将第二个条件分配给 4 时导致短路评估
第一个条件表达式,这意味着我为'a'和'value'都打印了3。
我使用 -std=c99 标志在带有 GCC 4.8.2 的 Lubuntu 14.04 上得到了上述结果。
感谢有人为我澄清这件事!

【问题讨论】:

  • 谁说“条件运算符从右到左求值”?

标签: c gcc


【解决方案1】:

条件运算符不会“从右到左求值”。标准 (C11 6.5.15/4) 说:

第一个操作数被求值;它之间有一个序列点 评估和第二个或第三个操作数的评估 (以评估为准)。只有当 首先比较不等于0;第三个操作数仅在 首先比较等于 0;结果是第二个或 第三个操作数(以被评估者为准)

所以表达式(a == 3) ? 3 : (a = 3) ? 5 : 0; 的计算步骤如下:

  • (a == 3) 结果是 0
  • (a = 3) 结果为3,不等于0
  • 5

所以5 是分配给value 的。

您可能会混淆如何评估条件运算符与条件运算符如何关联(或组)的概念。 C 的语法指定表达式:

(a == 3) ? 3 : (a = 3) ? 5 : 0;

像这样关联或分组子表达式:

((a == 3) ? 3 : ((a = 3) ? 5 : 0));

这通常被描述为“关联权利”。但是,这种分组/关联性不会影响表达式仍然从左到右计算并且第二个条件表达式仅在“外部”条件表达式中的第一个操作数被计算后计算的事实。

【讨论】:

  • 我明白了。因此,关联性并不能确定首先执行哪个操作。除非有一个复杂的表达式,它有副作用并且排列在幸运的位置,否则很难确定。每个表达式都是从左到右计算的吗?或者除了标准强加的排序规则之外,是否定义了订单实现?但是,一个运算符必须在某个地方获取一个值,所以如果假设它的优先级低于另一个运算符,它会必须首先被评估以提供一个值给另一个,对吧?
  • 该标准说“除非后面指定,子表达式的副作用和值计算是无序的”。正如您所说,如果子表达式的结果在表达式的大部分中使用,则必须首先对其进行评估。但是,计算可能提前多长时间并不总是很明显,并且对于 C 语言,没有一般要求表达式必须从左到右进行计算。还有关于副作用何时发生的问题,这些是由序列点规则控制的。
  • 天哪!好吧,我知道必须小心使用副作用,但是从更复杂的表达式中获取值似乎是一团糟!我会阅读标准草案关于评估的内容,但根据我的记忆,大部分都是以我无法理解的神秘方式编写的。另外,因为它的使用术语我还没有了解。非常感谢您的回答,如果我达到 15 个代表点,请投票!
  • 在我看来,最好将副作用大部分留给自己。编译器需要通过将副作用打包到更大的表达式中来帮助优化事物的日子已经基本结束。有时它可能是惯用的(例如*dst++ = *src++),或者语法可能会迫使您将多个副作用塞进一个表达式中(例如for 循环的控制子句),但在大多数情况下确实没多大需要。我认为多个副作用表达式通常也会使代码更难理解。
  • 很抱歉再次打扰您,但我突然想到,我基本上不知道是什么导致了我意想不到的结果,所以我的问题是无关紧要的。我认为将这个问题放在一个标题下会更有用:“评估顺序与表达式分组”或类似的东西。是否可以将其关闭为非建设性的或类似的,然后用另一个标题重新打开它?就我搜索它以了解有关此主题的更多信息而言,我找不到讨论此问题的线程。
【解决方案2】:

让我们一次追踪这一部分。你有这个表达式:

value = (a == 3) ? 3 : (a = 3) ? 5 : 0;

由于a从0开始,我们跳过第一个?:3分支,看第二个分支,即

(a = 3) ? 5 : 0

这里的条件是a = 3,它将a设置为3,然后计算出a的新值,即3。由于3非零,我们取?:的第一个分支,所以表达式的计算结果为5。最终结果是a 设置为 3,value 设置为 5。

语言规范保证评估顺序确实是您认为应该的 - ?: 运算符的“if”和“else”分支保证不会执行,除非条件正常运行,所以这里有序列点。我想你只是误解了a = 3操作的效果。

希望这会有所帮助!

【讨论】:

  • 我认为 评估顺序分组 是这里的关键。我不认为我在这里误解了 a = 3 的效果,但我可能是错的,因为我刚开始学习如何编程。我希望操作返回 true 并将值 3 存储在 a 变量中before 第一个条件运算符生效。
  • 我的意思是 a 评估为真,当然是非零,而不是值 true。 a = 3 应该返回 3...
【解决方案3】:

条件运算符从左到右计算(在任一分支之前计算条件)。您可能会将其与它的右结合性(似乎从右到左绑定)混淆了。

您的条件表达式基本上会产生以下逻辑:

if(a == 3) {
    value = 3;
} else {
    if(a = 3) {
        value = 5;
    } else {
        value = 0;
    }
}

请注意,条件在评估条件之前不会执行分支。

【讨论】:

  • 谢谢,我已经看过这个设置了,但并没有按照字面上的执行顺序。
猜你喜欢
  • 1970-01-01
  • 2017-07-16
  • 1970-01-01
  • 1970-01-01
  • 2023-04-11
  • 1970-01-01
  • 2012-02-22
  • 2013-04-26
  • 2013-06-06
相关资源
最近更新 更多