为什么这个版本的逻辑AND在C中没有显示短路行为?
是的,这是一个家庭作业的问题,但我已经做了我的研究和相当深入的思考这个话题,并不能解决这个问题。 这个问题指出,这段代码不会performance出短路行为,并问为什么。 但在我看来,它确实performance出短路的行为,所以有人可以解释为什么它没有?
在C:
int sc_and(int a, int b) { return a ? b : 0; }
在我看来,如果a
是假的,程序根本不会去评估b
,但是我一定是错的。 为什么在这种情况下,程序甚至碰到b
,什么时候不需要?
这是一个诡计的问题。 b
是sc_and
方法的input参数,因此将始终被评估。 换句话说, sc_and(a(), b())
会调用a()
并调用b()
(不保证顺序),然后调用sc_and
和a(), b()
a?b:0
。 它与三元运营商本身没有任何关系,这绝对是短路的。
UPDATE
关于为什么我把这称为“诡计问题”:这是因为缺乏明确的上下文来考虑“短路”(至less在OP中被重复)。 很多人只是在给定一个函数的定义时,就假定问题的背景是询问函数的主体 ; 他们往往不认为这个function本身就是一个expression。 这是问题的“窍门” 提醒大家,在编程方面,尤其是像C语言这样的语言,通常对规则有很多例外,你不能这么做。 例如,如果问题是这样提出的:
考虑下面的代码。 当从主调用时会sc_andperformance短路行为:
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; }
很明显,你应该考虑sc_and
作为一个运营商本身在你自己的领域特定的语言 ,并评估是否呼吁sc_and
展品短路行为像一个普通的&&
会 。 我根本不认为这是一个诡计的问题,因为显然你不应该把注意力集中在三元运算符上,而应该把重点放在C / C ++的函数调用机制上(而且,我猜测,很好地写入一个后续问题来写一个sc_and
,并做短路,这将涉及使用#define
而不是一个函数)。
无论您是否称三元运营商本身做短路(或其他方面,如“有条件评估”),取决于您对短路的定义,您可以阅读各种意见。 通过它我们可以做到,但这与实际问题并不相关,或者为什么我把它称为“技巧”。
当声明
bool x = a && b++; // a and b are of int type
执行,如果操作数a
评估为false
(短路行为), b++
将不被评估。 这意味着对b
的副作用不会发生。
现在看看这个函数:
bool and_fun(int a, int b) { return a && b; }
并称之为
bool x = and_fun(a, b++);
在这种情况下,无论a
是true
还是false
,在函数调用期间总是会对b++
进行评估,而对b
副作用将始终发生。
同样如此
int x = a ? b : 0; // Short circuit behavior
和
int sc_and (int a, int b) // No short circuit behavior. { return a ? b : 0; }
正如其他人已经指出的那样,不pipe是两个参数传入函数的什么东西,它都会在被传入的时候被评估,这是在十进制操作之前的方式。
另一方面,这个
#define sc_and(a, b) \ ((a) ?(b) :0)
将会 “短路”,因为这个macros并不意味着一个函数调用,并且不执行函数参数的评估。
编辑来纠正@cmasters评论中提到的错误。
在
int sc_and(int a, int b) { return a ? b : 0; }
… return
expression式确实会出现短路评估,但函数调用不会。
尝试打电话
sc_and (0, 1 / 0);
该函数调用评估1 / 0
,虽然它从来没有使用过,因此,可能会导致除以零误差。
(草案)ANSI C标准的相关摘录如下:
2.1.2.3程序执行
…
在抽象机器中,所有expression式都按照语义指定的方式进行评估。 如果一个实际的实现可以推断出它的值没有被使用,并且没有产生所需的副作用(包括由调用一个函数或者访问一个volatile对象引起的任何副作用),那么它就不需要评估一个expression式的一部分。
和
3.3.2.2函数调用
….
语义
…
在准备调用一个函数时,参数被评估,并且每个参数被分配相应参数的值。
我的猜测是,每个参数都是作为一个expression式来计算的,但是作为一个整体的参数列表并不是一个expression式,因此非SCE行为是强制性的。
作为C标准深层水面上的一种喷溅,我希望能够从两方面获得适当的信息:
- 评估1/0是否会产生未定义的行为?
- 参数列表是一个expression式吗? (我想不是)
PS
即使你转到C ++,并将sc_and
定义为inline
函数,也不会得到SCE。 如果你把它定义为一个Cmacros,就像@alk一样,你当然会这样做 。
要清楚地看到三元操作短路尝试稍微改变代码使用函数指针而不是整数:
int a() { printf("I'm a() returning 0\n"); return 0; } int b() { printf("And I'm b() returning 1 (not that it matters)\n"); return 1; } int sc_and(int (*a)(), int (*b)()) { a() ? b() : 0; } int main() { sc_and(a, b); return 0; }
然后编译它(即使几乎没有优化: -O0
!)。 如果a()
返回false,你会看到b()
没有被执行。
% gcc -O0 tershort.c % ./a.out I'm a() returning 0 %
这里生成的程序集如下所示:
call *%rdx <-- call a() testl %eax, %eax <-- test result je .L8 <-- skip if 0 (false) movq -16(%rbp), %rdx movl $0, %eax call *%rdx <- calls b() only if not skipped .L8:
所以正如其他人正确指出的那样,这个问题的关键在于让你专注于短路的三元运算符行为(称为“条件评估”),而不是在短时间内不进行参数评估(按值调用)。
C三元运算符决不会短路,因为它只计算单个expression式a (条件),以确定由expression式b和c给出的值,如果可能返回任何值。
以下代码:
int ret = a ? b : c; // Here, b and c are expressions that return a value.
这几乎等同于以下代码:
int ret; if(a) {ret = b} else {ret = c}
expression式a可以由其他运算符(例如&&或||)形成 可能会短路,因为它们可能会在返回值之前评估两个expression式,但这不会被认为是进行短路的三元运算符,而是在条件中使用的运算符,就像它在常规的if语句中一样。
更新:
关于三元运营商是一个短路运营商,有一些争议。 这个论点说,根据@aruisdante的评论,任何不评估所有操作数的操作符都会短路。 如果给出这个定义,那么三元运算符就会短路,如果这是我原来的定义,我同意。 问题在于术语“短路”最初用于允许这种行为的特定types的操作符,而这些操作符是逻辑/布尔操作符,而原因只是这些才是我将要解释的。
在“ 短路评估”一文之后 ,短路评估仅仅被称为在语言中实现的布尔运算符,其方式是知道第一个操作数会使第二个操作数不相关,这是因为&&操作符是第一个操作数是错误的 ,并为|| 运算符是第一个操作数, C115规范也在6.5.13逻辑AND运算符和6.5.14逻辑OR运算符中注明了它。
这意味着对于要识别的短路行为,如果第一个操作数与第二个操作数不相关,那么您应该期望在操作符中标识它,该操作符必须像布尔操作符一样评估所有操作数。 这与MathWorks在“逻辑短路”部分的短路的另一个定义中所写的内容是一致的,因为短路来自逻辑运算符。
正如我一直试图解释的C三元运算符,也称为三元如果,只评估两个操作数,它评估第一个,然后评估第二个,剩下的两个依赖于第一。 它总是这样做,它不应该在任何情况下评估所有三个,所以在任何情况下都没有“短路”。
与往常一样,如果你发现某些事情是不对的,请发表一个反对这个观点的评论,而不要只是一个倒退,只会让SO的经验更糟糕,我相信我们可以是一个更好的社区,只是低估了答案一个不同意。