N Log

Short-Circuit Evaluation (단락 평가)

들어가는 말

단락 평가는 표현식을 평가하는 도중 전체를 평가하지 않고도 미리 최종 결과를 결정할 수 있다면,
아직 평가하지 않은 부분은 건너뛰고 전체 식의 값을 확정하는 방식이다.

동작 원리

&& (AND 연산자)

AND 연산은 모든 조건이 참이어야 전체가 참이다.
좌항이 거짓이면 우항의 결과와 무관하게 전체 식은 거짓이므로, 우항은 평가하지 않는다.

좌항 우항 결과 우항 평가 여부
거짓 (무관) 거짓 평가 안 함
거짓 거짓 평가함
평가함
if (ptr != NULL && ptr->value > 0)

이 코드에서 ptrNULL이면 좌항이 거짓이 되어, 우항을 평가하지 않는다.

|| (OR 연산자)

OR 연산은 조건 하나만 참이어도 전체가 참이다.
좌항이 참이면 우항의 결과와 무관하게 전체 식은 참이므로, 우항은 평가하지 않는다.

좌항 우항 결과 우항 평가 여부
(무관) 평가 안 함
거짓 평가함
거짓 거짓 거짓 평가함
if (cached || load())

이 코드에서 좌항 cached가 참이면 우항 load()를 호출하지 않는다.

연산자 우선순위와 평가 순서는 다르다

단락 평가를 이해할 때는 연산자 우선순위와 평가 순서를 구분해야 한다.

  • 연산자 우선순위는 식이 어떻게 묶이는지를 정한다.
  • 평가 순서는 식을 실제로 계산할 때 각 부분을 어떤 순서로 평가하는지에 관한 규칙이다.

대부분의 연산자는 평가 순서를 보장하지 않는다

다음의 예제를 보자.

int x = f() + g() * h();

위 식은 *+보다 우선순위가 높기 때문에 다음처럼 묶인다.

int x = f() + (g() * h());

이 말은 g() * h()+의 오른쪽 피연산자로 묶인다는 뜻이다.
하지만 g()h()f()보다 먼저 호출된다는 뜻은 아니다.

C 표준은 +, * 같은 대부분의 연산자에서 피연산자의 평가 순서를 지정하지 않는다.
따라서 위 식에서 f(), g(), h() 중 어떤 함수가 먼저 호출될지는 정해져 있지 않다.
최종 결과는 f()의 결과와 g() * h()의 결과를 더하는 것이 맞지만, 함수 평가 순서는 보장되지 않는다.
따라서 표준 관점에서는 다음과 같은 함수 호출 순서가 모두 가능하다.

  1. f() -> g() -> h()
  2. f() -> h() -> g()
  3. g() -> f() -> h()
  4. g() -> h() -> f()
  5. h() -> f() -> g()
  6. h() -> g() -> f()

특히 이런 함수 호출이 값을 변경하는 부작용(side effect)을 가진다면 더 주의해야 한다.
같은 변수를 여러 번 변경하거나, 변경과 읽기가 순서 없이 섞이면 프로그래머의 예상과 다른 동작이 발생하기 쉽다.

&&||는 왼쪽에서 오른쪽으로 평가된다

반면 C의 &&||는 왼쪽에서 오른쪽으로 평가됨을 보장한다.

C99 표준 초안 N1256은 다음 내용을 명시한다.

  • 6.5.13p4 Logical AND operator

    Unlike the bitwise binary & operator,
    the && operator guarantees left-to-right evaluation;
    there is a sequence point after the evaluation of the first operand.
    If the first operand compares equal to 0, the second operand is not evaluated.

    비트 단위 이항 & 연산자와는 달리, && 연산자는 왼쪽에서 오른쪽으로 평가되는 것을 보장한다.
    첫 번째 피연산자의 평가가 끝난 뒤에는 시퀀스 포인트(sequence point)가 존재한다.
    첫 번째 피연산자가 0과 같다면(거짓이라면), 두 번째 피연산자는 평가되지 않는다.

  • 6.5.14p4 Logical OR operator

    Unlike the bitwise | operator,
    the || operator guarantees left-to-right evaluation;
    there is a sequence point after the evaluation of the first operand.
    If the first operand compares unequal to 0, the second operand is not evaluated.

    비트 단위 | 연산자와는 달리, || 연산자는 왼쪽에서 오른쪽으로 평가되는 것을 보장한다.
    첫 번째 피연산자의 평가가 끝난 뒤에는 시퀀스 포인트(sequence point)가 존재한다.
    첫 번째 피연산자가 0과 같지 않다면(참이라면), 두 번째 피연산자는 평가되지 않는다.

&&||를 함께 사용할 때

&&||를 각각 사용할 때의 동작은 앞에서 살펴본 바와 같다.
하지만 두 연산자가 하나의 식에 함께 있으면, 우선순위와 평가 순서를 혼동하기 쉽다.
다음 예제로 두 규칙이 어떻게 함께 적용되는지 살펴보자.

a || b && c

이 식은 &&||보다 연산자 우선순위가 높기 때문에 다음처럼 묶인다.

a || (b && c)

이때 &&의 연산자 우선순위가 더 높다고 해서 b && ca보다 먼저 평가되는 것은 아니다.
실제 평가는 왼쪽에서 오른쪽으로 다음과 같이 진행된다.

  1. a를 평가한다.
  2. a가 참이면 (b && c)는 평가하지 않고, 전체 식은 참이 된다.
  3. a가 거짓이면 오른쪽 피연산자인 (b && c)의 평가를 시작한다.
  4. (b && c)에서는 먼저 b를 평가한다.
  5. b가 거짓이면 c는 평가하지 않고, 전체 식의 결과는 거짓이 된다.
  6. b가 참이면 c를 평가하고, c의 결과로 전체 식의 결과가 결정된다.