development

C에서이 논리 AND 버전이 단락 동작을 표시하지 않는 이유는 무엇입니까?

big-blog 2020. 10. 9. 11:24
반응형

C에서이 논리 AND 버전이 단락 동작을 표시하지 않는 이유는 무엇입니까?


예, 이것은 숙제 질문이지만 주제에 대해 조사하고 상당한 양의 깊은 생각을했지만 이것을 알아낼 수 없습니다. 질문은이 코드 조각이 단락 동작을 나타내지 않으며 그 이유를 묻습니다. 그러나 그것은 단락 동작을 보이는 것처럼 보이므로 누군가가 왜 그렇지 않은지 설명 할 수 있습니까?

C에서 :

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

그것이 a거짓 인 경우 프로그램은 전혀 평가 b시도하지 않을 것 같지만 나는 틀 렸음에 틀림 없다. b이 경우 프로그램이 터치하지 않아도되는 이유는 무엇 입니까?


이것은 속임수 질문입니다. 메서드에 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단락 을 수행하는 후속 질문에 멋지게 작성하십시오 #define.

삼항 연산자 자체가 단락 (또는 '조건부 평가'와 같은 다른 것)을 수행하는 것을 호출하는지 여부는 단락에 대한 정의에 따라 다르며 이에 대한 다양한 의견을 읽을 수 있습니다. 내 생각에는 그렇긴하지만 실제 질문이나 내가 '트릭'이라고 부르는 이유와는별로 관련이 없습니다.


성명서

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입니다 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;
} 

이미 다른 사람들이 지적했듯이, 두 인수로 함수에 전달되는 것이 무엇이든 전달되는대로 평가됩니다. 이것이 테너 리 연산 이전의 방식입니다.

반면에 이것은

#define sc_and(a, b) \
  ((a) ?(b) :0)

이로, "단락" 매크로 함수 호출이와 함수의 인수없이 평가 (들) 수행을 의미하지는 않습니다.


@cmasters 주석에 표시된 오류를 수정하도록 편집되었습니다.


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

... returned 표현식은 단락 평가를 나타내지 만 함수 호출은 그렇지 않습니다.

전화 해보세요

sc_and (0, 1 / 0);

함수 호출 1 / 0은 사용되지 않지만 평가 하므로 0으로 나누기 오류가 발생할 수 있습니다.

ANSI C 표준 (초안)에서 발췌 한 관련 내용은 다음과 같습니다.

2.1.2.3 프로그램 실행

...

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

and

3.3.2.2 Function calls

....

Semantics

...

In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument.

My guess is that each argument is evaluated as an expression, but that the argument list as a whole is not an expression, hence the non-SCE behaviour is mandatory.

As a splasher on the surface of the deep waters of the C standard, I'd appreciate a properly informed view on two aspects:

  • Does evaluating 1 / 0 produce undefined behaviour?
  • Is an argument list an expression? (I think not)

P.S.

Even you move to C++, and define sc_and as an inline function, you will not get SCE. If you define it as a C macro, as @alk does, you certainly will.


To clearly see ternary op short circuiting try changing the code slightly to use function pointers instead of integers:

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;
}

And then compile it (even with almost NO optimization: -O0!). You will see b() is not executed if a() returns false.

% gcc -O0 tershort.c            
% ./a.out 
I'm a() returning 0
% 

Here the generated assembly looks like:

    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:

So as others correctly pointed out the question trick is to make you focus on the ternary operator behaviour that DOES short circuit (call that 'conditional evaluation') instead of the parameter evaluation on call (call by value) that DOES NOT short circuit.


The C ternary operator can never short-circuit, because it only evaluates a single expression a (the condition), to determine a value given by expressions b and c, if any value might be returned.

The following code:

int ret = a ? b : c; // Here, b and c are expressions that return a value.

It's almost equivalent to the following code:

int ret;
if(a) {ret = b} else {ret = c}

The expression a may be formed by other operators like && or || that can short circuit because they may evaluate two expressions before returning a value, but that would not be considered as the ternary operator doing short-circuit but the operators used in the condition as it does in a regular if statement.

Update:

There is some debate about the ternary operator being a short-circuit operator. The argument says any operator that doesn't evaluate all it's operands does short-circuit according to @aruisdante in the comment below. If given this definition, then the ternary operator would be short-circuiting and in the case this is the original definition I agree. The problem is that the term "short-circuit" was originally used for a specific kind of operator that allowed this behavior and those are the logic/boolean operators, and the reason why are only those is what I'll try to explain.

Following the article Short-circuit Evaluation, the short-circuit evaluation is only referred to boolean operators implemented into the language in a way where knowing that the first operand will make the second irrelevant, this is, for the && operator being the first operand false, and for the || operator being the first operand true, the C11 spec also notes it in 6.5.13 Logical AND operator and 6.5.14 Logical OR operator.

This means that for the short-circuit behavior to be identified, you would expect to identify it in an operator that must evaluate all operands just like the boolean operators if the first operand doesn't make irrelevant the second. This is in line with what is written in another definition for the short-circuit in MathWorks under the "Logical short-circuiting" section, since short-circuiting comes from the logical operators.

As I've been trying to explain the C ternary operator, also called ternary if, only evaluates two of the operands, it evaluates the first one, and then evaluates a second one, either one of the two remaining depending on the value of the first one. It always does this, its not supposed to be evaluating all three in any situation, so there is no "short-circuit" in any case.

As always, if you see something is not right, please write a comment with an argument against this and not just a downvote, that just makes the SO experience worse, and I believe we can be a much better community that one that just downvotes answers one does not agree with.

참고URL : https://stackoverflow.com/questions/26343355/why-is-this-version-of-logical-and-in-c-not-showing-short-circuit-behavior

반응형