【问题标题】:C multiple assignments to same variable in short-circuited expressionC在短路表达式中对同一变量进行多次赋值
【发布时间】:2020-08-27 21:19:54
【问题描述】:

我有三个变量:abc。假设它们是整数。我想以特定顺序找到其中的第一个非零值,而无需循环。以下似乎可行,但我不确定这是因为我很幸运,还是因为语言保证了它:

int main(int argc, char *argv[]) {
    int a = 0;
    int b = 3;
    int c = 5;
    int test;

    if ((test = a) != 0 || (test = b) != 0 || (test = c) != 0) {
        printf("First non-zero: %d\n", test);
    } else {
        printf("All zero!\n");
    }

    return 0;
}

此处显示的短路重复分配是否可以保证按预期工作,还是我遗漏了什么?

这可能是一个可以接受三个字母答案的地方,但两个字母的答案可能需要更多解释。

【问题讨论】:

  • 我会说它有效,但它很疯狂 - 尽管来自 Mad Physicist 是有道理的。
  • @chux-ReinstateMonica。忠于自己和所有人......还有什么选择会不那么疯狂?
  • @chux-ReinstateMonica:叫我疯狂查理:test = a + !!a * (b + !!b * c);

标签: c


【解决方案1】:

会的!

由于 OR 运算符的性质,如果任何条件是 true 则测试停止。

因此我认为你所做的基本上相当于:

test = a != 0 ? a : b != 0 ? b : c != 0 ? c : 0;
printf("%d\n",test);

但是你的看起来不错。

[更新]

根据 chqrlie 所说,它可以进一步简化为:

test = a ? a : b ? b : c;

【讨论】:

  • FWIW,我也喜欢你的替代措辞。
  • @MadPhysicist 也希望在未来的某一天成为一名疯狂的物理学家哈哈。谢谢你的问题。非常有趣。
  • 你作业中的最后一个测试是多余的。你可以简单地写:test = a ? a : b ? b : c;
  • @chqrlie 太棒了!感谢您让我知道更好的解决方案。我已经添加了您的解决方案,以便其他人可以参考
【解决方案2】:

是的,您的表达式已完全定义,因为在每个 || 运算符处都有一个序列点,并且短路评估保证分配给 test 的第一个非零值完成表达式。

这是一个没有序列点的疯狂替代方案,可能会产生无分支代码:

int test = a + !!a * (b + !!b * c);
printf("%d\n", test);

【讨论】:

  • 如果目的只是为了获得零或非零分支,你也可以这样做a*b*c。或者!!a * !!b * !!c,如果您担心溢出。不过,每个!! 都有可能产生一个分支。
  • @Lundin。目标是按特定顺序获得第一个非零值
  • 呃,我想我只是发明了对更糟糕代码的需求。 int* test; ... if (*(test = &a) != 0 || *(test = &b) != 0 || *(test = &c) != 0) { printf("First non-zero: %d, it was %s\n", *test, IT_WAS(test));#define IT_WAS(addr) (addr)==&a ? "a" : (addr)==&b ? "b" : "c"
  • @MadPhysicist 这就是我的意思,如果b=3; c=3 并且你得到3 作为输出,你不知道你是如何得到这个结果的。除非你在我上面的评论中使用了非常有问题的指针代码,否则就是......
  • @MadPhysicist 在这种情况下,我可以用一些questionable use of the comma operator, served together with some delicious setjmp spaghetti 来诱惑你吗?我相当确定这段代码具有明确定义的行为,但我可能会建议它通过一两次代码审查,然后再投入生产。
【解决方案3】:

代码是非常糟糕的做法,但保证可以正常工作。

这是因为 ||&& 运算符具有特殊特性 - 与 C 中的大多数运算符不同,它们保证左操作数的计算在右操作数的计算之前排序(执行)。这就是代码有效的原因。如果足以评估左侧操作数(“短路”),也可以保证不会评估右侧操作数。 C17 6.5.14/4 总结:

不同于按位 |运算符,||运算符保证从左到右的评估;如果 第二个操作数被求值,第一个求值之间有一个序列点 和第二个操作数。如果第一个操作数比较不等于 0,则第二个操作数为 未评估。

“序列点”是这里的关键,它为表达式提供了确定性的结果。

如果您使用了几乎任何其他运算符(例如按位 |),那么结果将是未定义的,因为您在同一个表达式中对同一个变量 test 有多个副作用(赋值)。

同一算法的更完善的版本将涉及将数据存储在数组中并循环遍历它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-04
    • 1970-01-01
    • 2015-03-29
    • 2014-12-30
    • 1970-01-01
    • 1970-01-01
    • 2015-04-10
    • 2017-04-21
    相关资源
    最近更新 更多