是的,这是一个作业问题,但我已经做了研究并深思熟虑了一段时间,但仍然无法解决。问题陈述说,这段代码不表现出短路行为,并问为什么。但在我看来,它确实表现出了短路行为,所以有人能解释一下为什么它没有吗?
在C中:
int sc_and(int a, int b) {
return a ? b : 0;
}
在我的看法中,如果a
为假,程序将不会尝试评估b
,但我一定是错的。为什么程序在这种情况下甚至要触及b
,而它并不必须这么做?
是的,这是一个作业问题,但我已经做了研究并深思熟虑了一段时间,但仍然无法解决。问题陈述说,这段代码不表现出短路行为,并问为什么。但在我看来,它确实表现出了短路行为,所以有人能解释一下为什么它没有吗?
在C中:
int sc_and(int a, int b) {
return a ? b : 0;
}
在我的看法中,如果a
为假,程序将不会尝试评估b
,但我一定是错的。为什么程序在这种情况下甚至要触及b
,而它并不必须这么做?
这是一个有技巧性的问题。 b
是 sc_and
方法的输入参数,因此将始终被评估。换句话说,sc_and(a(), b())
将调用 a()
和调用 b()
(顺序不保证),然后使用 a(), b()
的结果调用 sc_and
,该结果传递给 a?b:0
。这与三元运算符本身无关,它绝对会短路。
更新
关于为什么我称这个问题为“有技巧性的问题”:这是因为缺乏明确定义的上下文来考虑“短路”(至少是由 OP 复制的)。许多人在只给出函数定义时,假设问题的上下文是询问函数的主体;他们通常不将函数视为一个表达式本身。这就是问题的“技巧”所在;提醒您,在编程中,特别是在像 C 一样经常有许多例外规则的语言中,您不能这样做。例如,如果问题是这样问的:
请考虑以下代码。当从main调用sc_and时,它会表现出短路行为吗?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
而不是函数)。
无论你是否称三元运算符本身所做的事情为短路(或其他什么,比如“条件评估”),这取决于你对短路的定义,你可以阅读各种评论来了解思考。在我的定义中,它确实是短路,但这与实际问题或我称其为“诡计”的原因并不相关。
if (condition) {当条件为真} else {当条件为假}
一样。这并不被称为短路操作。 - Jensx Sand y
(使用Sand表示短路变量)等价于条件表达式if x then y else false;
,表达式x Sor y
等价于if x then true else y
。(参见http://en.wikipedia.org/wiki/Short-circuit_evaluation)。 - haccks?:
操作符是一个表达式,“if-else”是一个语句。虽然你可以构造一组等效的语句来产生与三元表达式相同的效果,但在语义上它们是非常不同的事情。这就是为什么它被认为是短路的原因;大多数其他表达式总是评估它们所有的操作数(+,-,*,/
等)。当它们不这样做时,便是短路(&&, ||
)。 - aruisdante当这个陈述被执行时
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++
都将在函数调用期间始终被评估1,并且 b
的副作用将始终发生。int x = a ? b : 0; // Short circuit behavior
和
int sc_and (int a, int b) // No short circuit behavior.
{
return a ? b : 0;
}
函数参数的求值顺序是未指定的。
int x = a ? b++ : 0
作为可观察的短路。 - Paul Draper正如其他人所指出的,无论作为两个参数传递给函数的是什么内容,它都会按照传递时的形式进行评估,这发生在三元操作之前。
另一方面,这个...
#define sc_and(a, b) \
((a) ?(b) :0)
would会“短路”,因为这个宏并不意味着一个函数调用,因此不会对函数的参数进行求值。已编辑以纠正@cmasters评论中指出的错误。
在
int sc_and(int a, int b) {
return a ? b : 0;
}
...return
表达式确实展示了短路求值,但函数调用并没有。
尝试调用
sc_and (0, 1 / 0);
1/0
,尽管它从未被使用,因此可能会导致除以零的错误。1 / 0
是否会产生未定义行为?P.S.
即使您转移到C ++并将sc_and
定义为inline
函数,您也不会获得SCE。如果您像@alk那样将其定义为C宏,那么肯定会获得。
inline
函数不会改变函数调用的语义。而这些语义指定所有参数都被评估,正如您正确引用的那样。即使编译器可以优化调用,可见行为也不能改变,即sc_and(f(),g())
必须表现得好像f()
和g()
总是被调用。sc_and(0,1/0)
是一个糟糕的例子,因为它是未定义的行为,编译器甚至不需要调用sc_and()
... - cmaster - reinstate monicainline
保留了调用语义。我选择了零除,因为它符合示例,并且我认为 SCE 经常被利用来避免未定义的行为。 - Thumbnail要清楚地看到三元运算符的短路,请稍微更改代码,使用函数指针而不是整数:
```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:
正如其他人正确指出的那样,这个问题的技巧在于让您关注三元运算符的行为,它会进行条件评估并且具有短路功能 (称之为'条件评估'),而不是调用时的参数评估 (传值调用),它没有短路功能。
int ret = a ? b : c; // Here, b and c are expressions that return a value.
这几乎等同于以下代码:
int ret;
if(a) {ret = b} else {ret = c}
表达式a可以由其他运算符(如&&或||)组成,这些运算符可能会短路,因为它们在返回值之前可能会评估两个表达式,但这不会被视为三元运算符进行短路,而是像常规if语句中使用的条件运算符。
更新:
有关三元运算符是否为短路运算符存在一些争议。该论点认为,根据下面评论中@aruisdante的说法,任何不评估其所有操作数的运算符都会进行短路。如果给定此定义,则三元运算符将进行短路,在这种情况下,如果这是最初的定义,我同意。问题在于,“短路”一词最初用于允许此行为的特定类型的运算符,这些运算符是逻辑/布尔运算符,而仅限于这些运算符的原因是我将尝试解释的原因。
根据短路求值文章,短路求值仅适用于语言中实现的布尔运算符,其中第一个操作数确定后,第二个操作数将变得无关紧要。对于第一个操作数为false的&&运算符和第一个操作数为true的||运算符,C11规范也在6.5.13逻辑AND运算符和6.5.14逻辑OR运算符中进行了说明。正如我一直试图解释的C三元运算符(也称为三元if),只评估两个操作数,它评估第一个操作数,然后评估第二个操作数,取决于第一个操作数的值是两个剩余值之一。它始终这样做,在任何情况下都不应该评估所有三个运算数,因此在任何情况下都没有“短路”。
一如既往,如果您发现有什么不对的地方,请撰写一条评论并提出反对意见,而不仅仅是点个踩,这只会使SO的体验更糟糕,我相信我们可以成为一个更好的社区,而不是只是不同意答案就点踩的社区。
x = a ? b : 0;
在语义上等同于(a && (x = b)) || (x = 0);
。 - jxh
AND()
的函数,但是通过值接收参数并根据函数逻辑计算(或不计算)的函数随处可见。尽管这是一个“恶作剧问题”,但这是理解C语言的关键行为。在一个采用按名称调用的语言中,这个问题的答案会有很大不同。 - Ben JacksonIIf(x Is Nothing, Default, x.SomeValue)
这样的代码。 - Dan Bryant