85.17 시퀀스 포인트

C 언어에서 모든 식의 계산(평가)이 완료되는 지점을 시퀀스 포인트라고 합니다. 그리고 시퀀스 포인트 사이에서 식의 값은 한 번만 수정될 수 있습니다. 내용이 좀 어렵죠? 먼저 대표적인 시퀀스 포인트는 ; (세미콜론)인데 다음 코드는 세미콜론 사이에서 값이 두 번 수정된 잘못된 코드입니다.

//       ↓ 시퀀스 포인트
int i = 0;

i = i++;
//     ↑ 시퀀스 포인트

코드가 여러 줄로 되어 있지만 중간에 빈 줄은 무시하고 세미콜론 사이의 식만 보면 됩니다. i = i++ 식을 보면 ii = i에서 값이 한 번 수정되고 i++에서 다시 수정됩니다(사실 i = i의 결과는 바뀐게 없지만 일단 값을 할당했으므로 값이 수정된 것입니다).

int i = 0;

//↓ 값이 수정됨
i = i++;
//   ↑ 값이 수정됨

이 코드는 Visual Studio에서는 경고 없이 컴파일되지만 GCC에서는 -Wsequence-point 또는 -Wall옵션을 사용하여 경고를 출력할 수 있습니다.

$ gcc main.c -Wsequence-point
main.c: In function ‘main’:
main.c:7:7: warning: operation on ‘i’ may be undefined [-Wsequence-point]
     i = i++;
       ^

i = i++과 같이 의도가 명확하지 않은 코드를 정의되지 않은 행동(undefined behavior)라 부르는데 컴파일러에 따라 결과가 달라질 수 있으므로 이런 코드의 작성은 피해야 합니다.

물론 다음과 같이 ++ 연산자를 사용한 뒤 다른 변수에 저장하는 것은 올바른 코드입니다. 왜냐하면 ji 모두 한 번씩 수정되기 때문입니다.

int i = 0;
int j;

j = i++;

다음은 C 언어의 모든 시퀀스 포인트입니다.

표 85-7 시퀀스 포인트
이름 구문 설명
논리 AND 연산자 && && 연산자 앞에서 모든 식의 계산이 완료되며 첫 번째 식의 결과에 따라 두 번째 식은 평가하지 않고 넘어가는 단락 평가를 수행합니다.
논리 OR 연산자 || || 연산자 앞에서 모든 식의 계산이 완료되며 첫 번째 식의 결과에 따라 두 번째 식은 평가하지 않고 넘어가는 단락 평가를 수행합니다.
쉼표 연산자 , , 연산자 앞에서 모든 식의 계산이 완료됩니다. 따라서 다음과 같이 i++i++을 콤마로 구분한 코드는 올바른 코드입니다.
// 시퀀스 포인트 사이에서 값이 두 번 수정됨. 정의되지 않은 행동
x = (i++ + i++, i += 2);

// 시퀀스 포인트 사이에서 값이 한 번 수정됨. 올바른 코드
x = (i++, i++, i += 2);
함수 호출 연산자 ( ) 함수 호출 직전까지 모든 식의 계산이 완료됩니다. 다음과 같이 함수 앞에서 i++가 수행되고, 함수의 인수에서 i++가 수행되어 값이 두 번 수정되었습니다. 즉, 함수 호출 연산자 ( )의 안쪽뿐만 아니라 함수 바깥의 식도 함께 평가됩니다.
#include <stdio.h>
 
int function(int n)
{
    return n;
}
 
int main()
{
    int i = 0;
    int x;
 
    x = i++ + function(i++); // 함수 호출 전에 값이 두 번 수정됨
                             // 정의되지 않은 행동
 
    return 0;
} 
삼항 연산자 ? ?로 조건을 판단하기 전까지 모든 식의 계산이 완료됩니다.
세미콜론 ; ; 직전에 모든 식의 계산이 완료됩니다.
if, switch의 조건식 if ( )
switch ( )
if, switch에 연결된 코드가 실행되기 직전에 모든 식의 계산이 완료됩니다.
while, do while의 조건식 while ( )
do while ( )
반복될 코드가 실행되기 직전에 모든 식의 계산이 완료됩니다.
for 제어문의 세미콜론 for ( ; ; ) 각 세미콜론 직전에 모든 식의 계산이 완료됩니다(반복될 코드가 실행되기 직전에도 모든 식의 계산이 완료됩니다).
함수의 반환 return return으로 값을 반환하기 직전에 모든 식의 계산이 완료됩니다.