为什么C版本的逻辑AND不显示短路行为?

Bob*_*ski 79 c short-circuiting

是的,这是一个家庭作业问题,但我已经完成了我的研究并对这个主题进行了相当深入的思考,并且无法弄清楚这一点.问题是这段代码没有表现出短路行为,并询问原因.但它在我看来它确实表现出短路行为,所以有人可以解释为什么它没有?

在C:

int sc_and(int a, int b) {
    return a ? b : 0;
}
Run Code Online (Sandbox Code Playgroud)

在我看来,如果这a是假的,程序根本不会尝试评估b,但我一定是错的.为什么程序b在这种情况下甚至不需要触摸?

aru*_*nte 117

这是一个棘手的问题.b是方法的输入参数sc_and,因此将始终进行求值.换句话说,sc_and(a(), b())将调用a()和调用b()(命令不保证),然后调用传递给sc_and的结果.它与三元运算符本身无关,它绝对会短路.a(), b()a?b:0

UPDATE

关于为什么我称之为"技巧问题":这是因为缺乏明确定义的背景来考虑"短路"(至少由OP再现).当给出一个函数定义时,许多人假设问题的上下文是在询问函数的主体 ; 他们通常不认为这个功能本身就是一种表达.这是问题的"诀窍"; 提醒你,在一般的编程中,尤其是像C-like这样的语言,通常有很多规则例外,你不能这样做.例如,如果问题是这样的:

请考虑以下代码.从main调用时sc_and exibit短路行为:

int sc_and(int a, int b){
    return a?b:0;
}

int a(){
    cout<<"called a!"<<endl;
    return 0;
}

int b(){
    cout<<"called b!"<<endl;
    return 1;
}

int main(char* argc, char** argv){
    int x = sc_and(a(), b());
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

很明显,你应该用sc_and自己的特定领域语言来考虑作为一个运算符,并评估调用是否sc_and表现出像常规&&那样的短路行为.我根本不认为这是一个技巧问题,因为很明显你不应该专注于三元运算符,而是应该专注于C/C++的函数调用机制(我猜,很好地写一个后续问题,写一个sc_and短路,这将涉及使用一个#define而不是一个功能).

你是否称之为三元运算符本身的短路(或其他类似的东西,如'条件评估')取决于你对短路的定义,你可以阅读各种意见,以便对此进行思考.它确实如此,但它与实际问题并不是非常相关,或者为什么我称它为"技巧".

  • @Jens:我会调用任何一个操作员以一种"短路"形式跳过对其一个或多个操作数的评估的情况,但这实际上只是你选择如何定义这个术语的问题. (17认同)
  • @Jens它是``if else``的语法糖,而不是``if``.即使这样,它也不是真的; `?:`运算符是*表达式*,``if-else``是*语句*.这些在语义上是非常不同的东西,即使你可以构造一组等效的*语句*,它们产生与三元*表达式*相同的效果.这就是它被认为是短路的原因; 大多数其他*表达式*总是评估它们的所有操作数(``+, - ,*,/``等).当他们不这样做时,这是一个短路(``&&,||``). (13认同)
  • 三元运算符是短路的吗?不.它评估`?`后面的一个表达式,就像`if(condition){when true} else {when-false}`一样.这不称为短路. (12认同)
  • @Jens; 三元运算符是短路的吗?是.事实上确实如此.[短路表达式`x Sand y`(使用Sand表示短路变量)相当于条件表达式`if x then y else false;`表达式`x Sor y`相当于`if x然后是真的其他y`.](http://en.wikipedia.org/wiki/Short-circuit_evaluation). (12认同)
  • 是的,对我而言,重要的区别在于我们在谈论运营商.当然*流控制语句*控制执行哪个代码路径.对于操作员来说,对于不熟悉C和C派生语言的人来说,操作员可能无法评估其所有操作数是不明显的,因此有一个术语来讨论这个属性很重要,为此,我使用"short"短路". (9认同)
  • @Jens我会在一开始就与你达成一致,但似乎"短路"是指运营商,特别是那些可能无法评估所有操作数的运营商.`?:`运算符符合这个定义.`if-else`不是运营商. (4认同)
  • @haccks:同样来自你的链接:"短路评估......表示某些编程语言中某些**布尔**运算符的语义"(强调我的).人们不会说`if(a())返回b(); return false;`"short circuts"尽管它在语义上与`return a()&& b(); (3认同)
  • @ths:不允许内联影响可观察行为.因此,如果评估调用的第二个参数有副作用,则内联*之后的函数必须*仍然评估它,而不管`a`的值. (2认同)
  • @R ..我只调用`&&`和`||'短路.如果你调用`?:`short circuitnig,你必须调用`if()`短路,因为`?:`只是`if`的语法糖. (2认同)
  • @aruisdante:条件运算符绝对不是'if else`的"语法糖". (2认同)

hac*_*cks 42

当声明

bool x = a && b++;  // a and b are of int type
Run Code Online (Sandbox Code Playgroud)

执行,b++如果操作数被a评估为false(短路行为),则不会被评估.这意味着b不会发生副作用.

现在,看看功能:

bool and_fun(int a, int b)
{
     return a && b; 
}
Run Code Online (Sandbox Code Playgroud)

并称之为

bool x = and_fun(a, b++);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,无论atruefalse,b++将永远是函数调用和副作用的评估过程中b总会发生.

同样如此

int x = a ? b : 0; // Short circuit behavior 
Run Code Online (Sandbox Code Playgroud)

int sc_and (int a, int b) // No short circuit behavior.
{
   return a ? b : 0;
} 
Run Code Online (Sandbox Code Playgroud)

  • @ShivanDragon这听起来像改变了我的观察行为. (4认同)
  • @ShivanDragon:内联编译器时不会改变行为.它不像简单地替换函数体那样简单. (3认同)

alk*_*alk 19

正如其他人已经指出的那样,无论什么作为两个参数传递给函数,它都会在传入时进行评估.这是在tenary操作之前的方式.

另一方面,这个

#define sc_and(a, b) \
  ((a) ?(b) :0)
Run Code Online (Sandbox Code Playgroud)

"短路",因为这个并不意味着函数调用,并且这样就不会对函数的参数进行评估.


Thu*_*ail 5

编辑以纠正@cmasters评论中提到的错误.


int sc_and(int a, int b) {
    return a ? b : 0;
}
Run Code Online (Sandbox Code Playgroud)

... returned表达式确实显示短路评估,但函数调用没有.

试着打电话

sc_and (0, 1 / 0);
Run Code Online (Sandbox Code Playgroud)

函数调用1 / 0虽然从未使用过,但仍会进行评估,从而导致 - 可能 - 除以零错误.

(草案)ANSI C标准的相关摘录如下:

2.1.2.3程序执行

...

在抽象机器中,所有表达式都按语义指定进行计算.实际实现不需要评估表达式的一部分,如果它可以推断出它的值未被使用并且不产生所需的副作用(包括由调用函数或访问易失性对象引起的任何副作用).

3.3.2.2函数调用

....

语义

...

在准备对函数的调用时,将评估参数,并为每个参数分配相应参数的值.

我的猜测是每个参数都被计算为一个表达式,但是参数列表作为一个整体不是表达式,因此非SCE行为是强制性的.

作为C标准深水表面的一个飞溅物,我很欣赏两个方面的适当信息:

  • 评估是否会1 / 0产生不确定的行为?
  • 参数列表是一个表达式吗?(我想不是)

PS

即使你转向C++,并定义sc_and为一个inline函数,你也不会得到SCE.如果你将它定义为C宏,就像@alk一样,你肯定会.