是的,这是一个家庭作业问题,但是我已经完成了我的研究并且对这个主题进行了大量的深入思考,无法解决。问题指出,这段代码没有表现出短路行为,并询问原因。但是在我看来,它确实表现出短路行为,所以有人可以解释为什么不这样做吗?
在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
在这种情况下,为什么程序在不需要的情况下甚至可以触摸?
AND()
,但是到处都是按值接收参数然后根据函数的逻辑对参数进行评估(或不评估)的函数。尽管是一个“技巧性问题”,但理解它仍然是C的关键行为。用按名字呼叫的语言,这个问题会有完全不同的答案。
IIf(x Is Nothing, Default, x.SomeValue)
。
Answers:
这是一个技巧问题。b
是方法的输入参数sc_and
,因此将始终进行评估。换句话说,sc_and(a(), b())
将呼叫a()
和呼叫b()
(不能保证顺序),然后将呼叫sc_and
的结果a(), b()
传递到a?b:0
。它与三元运算符本身无关,后者绝对会短路。
更新
关于我为什么将其称为“技巧性问题”的原因:这是因为缺乏在何处考虑“短路”的明确定义的上下文(至少由OP复制)。许多人,仅给出函数定义时,就认为问题的上下文是在询问函数的主体。他们常常不认为函数本身就是一种表达。这是问题的“窍门”。提醒您,在一般编程中,尤其是在C语言之类的语言中,规则通常有很多例外,您不能这样做。例如,如果这样问问题:
考虑下面的代码。从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;
}
这将是立即清楚,你应该要思考sc_and
在你自己本身和运营商领域特定语言和评估,如果调用sc_and
展品短路行为像一个普通的&&
会。我根本不认为这是一个棘手的问题,因为很明显您不应该专注于三元运算符,而应该专注于C / C ++的函数调用机制(而且,我猜想是很好地跟进一个后续问题,编写一个sc_and
确实短路的问题,这将涉及使用a#define
而非函数)。
是否调用三元运算符本身会造成短路(或其他诸如“条件评估”)的操作,取决于您对短路的定义,您可以阅读各种注释以获取相关的想法。从我的角度来看确实如此,但它与实际问题或我为何称其为“技巧”并不十分相关。
?
,就像中的if (condition) {when true} else {when-false}
。这不称为短路。
x Sand y
(使用砂来表示短路品种)等效于条件表达式if x then y else false;
的表达式x Sor y
相当于if x then true else y
。。
if else
“而不是”的语法糖if
。即使这样,它并不是真的。该?:
运营商是表达,if-else
是一个声明。即使您可以构造与三元表达式产生相同效果的等效语句集,这些语句在语义上也大不相同。这就是为什么它被认为是短路的原因。其他大多数表达式始终求值其所有操作数(等)。如果没有,则表示短路()。+,-,*,/
&&, ||
当陈述
bool x = a && b++; // a and b are of int type
执行,b++
如果操作数的a
计算结果为false
(短路行为),则不会进行评估。这意味着b
不会发生副作用。
现在,看一下函数:
bool and_fun(int a, int b)
{
return a && b;
}
并称之为
bool x = and_fun(a, b++);
在这种情况下,在函数调用期间,将始终将a
istrue
或评估为1,并且始终会发生副作用。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;
}
1未指定函数参数的评估顺序。
int x = a ? b++ : 0
,作为可观察到的短路。
正如其他人已经指出的那样,无论将什么作为两个参数传递给函数,都将在传递参数时对其求值。这是在进行整数运算之前。
另一方面,这
#define sc_and(a, b) \
((a) ?(b) :0)
会“短路”,因为此宏并不意味着函数调用,并且因此不执行函数自变量的求值。
编辑以更正@cmasters注释中指出的错误。
在
int sc_and(int a, int b) {
return a ? b : 0;
}
... return
ed表达式确实具有短路评估功能,但函数调用没有。
尝试致电
sc_and (0, 1 / 0);
尽管函数调用1 / 0
从未使用过,但它的求值结果为-可能是-除零错误。
ANSI C标准草案的相关摘录是:
2.1.2.3程序执行
...
在抽象机中,所有表达式均按语义指定的方式求值。如果实际实现可以推断出未使用表达式的值并且没有产生所需的副作用(包括由调用函数或访问易失性对象引起的副作用),则无需评估表达式的一部分。
和
3.3.2.2函数调用
....
语义学
...
在准备调用函数时,将评估参数,并为每个参数分配相应参数的值。
我的猜测是,每个参数都作为一个表达式求值,但整个参数列表不是一个表达式,因此非SCE行为是强制性的。
作为C标准深水表面的溅水工具,我希望从两个方面获得正确的见解:
1 / 0
产生不确定的行为吗?聚苯乙烯
即使您使用C ++并定义sc_and
为inline
函数,也不会获得SCE。如果将其定义为C宏(如@alk一样),当然可以。
inline
函数不会改变函数调用的语义。正如您正确引用的那样,这些语义指定对所有参数进行求值。即使编译器可以优化调用,可见的行为也不得更改,即sc_and(f(), g())
必须表现得好像两者都一样f()
并且g()
始终被调用。sc_and(0, 1/0)
这是一个不好的例子,因为它是未定义的行为,甚至不需要编译器调用sc_and()
...
inline
保留了调用语义。我坚持零除法,因为它适合示例,我认为经常利用SCE来避免不确定的行为。
为了清楚地看到三元运算短路,请尝试稍微更改代码以使用函数指针而不是整数:
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
!)。b()
如果a()
返回false,您将看到未执行。
% 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三进制运算符永远不会短路,因为它仅求值单个表达式a(条件),以确定由表达式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}
表达式a可以由&&或||等其他运算符形成 之所以会短路,是因为它们可能会在返回值之前先对两个表达式求值,但是这不会被视为三元运算符发生短路,而是像常规if语句中那样在条件中使用的运算符。
更新:
关于三元运算符是短路运算符存在一些争论。该参数表示,任何不评估其所有操作数的运算符都会根据以下注释中的@aruisdante短路。如果给出这个定义,那么三元运算符将短路,在这种情况下,我同意。问题是“短路”一词最初用于允许这种行为的特定类型的运算符,而这些运算符是逻辑/布尔运算符,而为什么只有这些是我将尝试解释的原因。
在“短路评估”一文之后,短路评估仅以已知第一个操作数将使第二个不相关的方式引用到在语言中实现的布尔运算符,这是因为&&运算符是第一个操作数为false,对于|| 运算符是第一个操作数true,C11规范还在6.5.13逻辑AND运算符和6.5.14逻辑OR运算符中进行了注明。
这意味着要识别短路行为,如果第一个操作数与第二个操作数无关,那么您将期望在必须评估所有操作数的运算符中进行识别,就像布尔运算符一样。这与MathWorks中“逻辑短路”部分下关于短路的另一种定义所写的内容一致,因为短路来自逻辑运算符。
正如我一直试图解释的C三元运算符(也称为三元运算符)一样,它仅求两个操作数,它求第一个,然后求第二个,剩下的两个取决于其中一个的值。第一。它总是这样做,在任何情况下都不应该评估所有这三个,因此在任何情况下都不会出现“短路”。
与往常一样,如果您发现某些问题是不对的,请发表评论,并对此提出反对意见,而不仅要讨价还价,这只会使SO体验变得更糟,我相信我们可以成为一个更好的社区,而那些人只需对问题进行投票就可以了一个不同意。
x = a ? b : 0;
在语义上与(a && (x = b)) || (x = 0);