Short-Circuit Evaluation (단락 평가)
들어가는 말
단락 평가는 표현식을 평가하는 도중 전체를 평가하지 않고도 미리 최종 결과를 결정할 수 있다면,
아직 평가하지 않은 부분은 건너뛰고 전체 식의 값을 확정하는 방식이다.
동작 원리
&& (AND 연산자)
AND 연산은 모든 조건이 참이어야 전체가 참이다.
좌항이 거짓이면 우항의 결과와 무관하게 전체 식은 거짓이므로, 우항은 평가하지 않는다.
| 좌항 | 우항 | 결과 | 우항 평가 여부 |
|---|---|---|---|
| 거짓 | (무관) | 거짓 | 평가 안 함 |
| 참 | 거짓 | 거짓 | 평가함 |
| 참 | 참 | 참 | 평가함 |
if (ptr != NULL && ptr->value > 0)
이 코드에서 ptr이 NULL이면 좌항이 거짓이 되어, 우항을 평가하지 않는다.
|| (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()의 결과를 더하는 것이 맞지만, 함수 평가 순서는 보장되지 않는다.
따라서 표준 관점에서는 다음과 같은 함수 호출 순서가 모두 가능하다.
f()->g()->h()f()->h()->g()g()->f()->h()g()->h()->f()h()->f()->g()h()->g()->f()
특히 이런 함수 호출이 값을 변경하는 부작용(side effect)을 가진다면 더 주의해야 한다.
같은 변수를 여러 번 변경하거나, 변경과 읽기가 순서 없이 섞이면 프로그래머의 예상과 다른 동작이 발생하기 쉽다.
&&와 ||는 왼쪽에서 오른쪽으로 평가된다
반면 C의 &&와 ||는 왼쪽에서 오른쪽으로 평가됨을 보장한다.
C99 표준 초안 N1256은 다음 내용을 명시한다.
-
6.5.13p4 Logical AND operatorUnlike 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 operatorUnlike 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 && c가 a보다 먼저 평가되는 것은 아니다.
실제 평가는 왼쪽에서 오른쪽으로 다음과 같이 진행된다.
a를 평가한다.a가 참이면(b && c)는 평가하지 않고, 전체 식은 참이 된다.a가 거짓이면 오른쪽 피연산자인(b && c)의 평가를 시작한다.(b && c)에서는 먼저b를 평가한다.b가 거짓이면c는 평가하지 않고, 전체 식의 결과는 거짓이 된다.b가 참이면c를 평가하고,c의 결과로 전체 식의 결과가 결정된다.