본문 바로가기

카테고리 없음

C 에서 i = ++i 나 a[i++] = i 를 쓰면 안되는 이유

표현식 i = ++i 나 a[i++] = i 를 쓰게될 경우 이는 C 표준에서 undefined behavior 입니다. 

 

i = ++i 에 대해 GCC 컴파일러가 경고줌.
a[i++] = i 에 대해 GCC 컴파일러가 경고줌.

 

 

이식성 있는 코드를 작성하고 싶다면 위와 같은 표현식은 피하는게 좋습니다.

그렇다면 왜 undefined behavior 일까요 ? 

이를 위해 side effect , sequence point 라는 개념에 대해 먼저 알아 봅시다.

 

 

1. side effect 

 

우리는 보통 연산자가 피연산자의 값을 바꿀 것이라고 기대하지는 않습니다. 

가장 평범한 연산자인 산술연산자의 경우

피연산자 i , j 에 대해 특정 연산을 하지 i , j 값 자체를 바꾸지는 않습니다. 

 

 

#include <stdio.h> 
int main(void){

  int i = 1 , j = 1 ; 
  
  i + j ; 
  i - j ; 
  i * j ; 
  i / j ; 
  
  printf("%d %d\n" , i , j ) ; // 1 1 출력 
  
}

 

 

하지만 몇몇의 연산자는 피연산자 값을 바꿀 수 있습니다.  

우리는 그러한 것들을 side effect ( 부수 효과 ) 를 가진 연산자라고 합니다. 

연산자의 본래 기능인 연산외에 피연산자의 값을 바꾸는 기능까지 있어서 그렇게 부릅니다. 

 

side effect 를 가진 연산자에는 할당연산자(=) , 증감연산자(++,--) 가 있습니다. 

 

할당 연산자의 

1) 연산 결과는 오른쪽 피연산자의 값이고

2) side effect 로 왼쪽 피연산자의 값을 오른쪽 피연산자의 값으로 바꿉니다.  

 

증가연산자는 

1 - 1) 연산자가 피연산자보다 먼저 올 경우 ( prefix ) 연산 결과 값은 (피연산자의 값 + 1)

1 - 2) 연산자가 피연산자보다 나중에 올 경우 ( postfix ) 연산 결과 값은 (피연산자의 값) 이며 

2) side effect 로 피연산자의 값을 1 증가 시킵니다. 

(감소 연산자도 비슷한 방식) 

 

#include <stdio.h> 
int main(void){

 int a = 0 ; // 0 으로 초기화  
 printf("%d " , a = 3 ); 
 printf("%d\n" , a ) ; 
 // 출력 :3 3 
 // a = 3 표현식에서 할당 연산자의 연산의 결과는 3 이며 side effect 로 a 의 값을 3 으로 바꾼다.
    
 int b = 0 ; // 0 으로 초기화 
 printf("%d " , ++b) ; 
 printf("%d\n" , b) ; 
 // 출력 :1 1 
 // ++b 표현식에서 증가 연산자의 연산의 결과는 1 이며 side effect 로 b 의 값을 1증가 시킨다.
    
 }

 

2. sequence point

 

 

 

sequence point 란

이전에 연산자에 의해 일어난 side effect 가 수행되었음을 보장하는 프로그램 실행상의 부분을 말합니다.

 

sequence point A  > 할당 연산자 연산  > normal point B  > 증감 연산자 연산  > sequence point C

 

지점 A 와 C 사이에는 side effect 가 있는 연산자가 2개 있습니다.  

side effect 정확히 언제 일어날 지는 unspecified behavior 입니다. 

즉 , 시스템마다 케이스마다 다를 수 있습니다. 

따라서 지점 B에서 side effect 가 몇 번 수행 되었는지 보장하지 못합니다. 

 

다만 , sequence point 인 지점 C에서 A 와 C 사이에 있는 연산자에 의한 side effect 가 모두 일어났다고는 말할 수 있습니다. 

 

흔한 sequence point 로는

표현문(expression statement) 의 끝  예)  b = a + c ;    

논리 연산자 ( && , || )  ,  콤마 연산자 ( , ) 

인자(argument) 가 연산된 후 함수를 호출할 때 가 있습니다. 

 

다음은 표준에서 제시하는 sequence point 들 입니다.

 

 

 

ISO/IEC C99 reference p437

 

 

3. i = i++ 와 a[++i] = i 가 undefined behavior 인 이유  

 

 

표준에서는 이와 관련하여 다음의 2가지 원칙을 제시하였습니다. 

 

 

1. Between the previous and next sequence point an object shall have its stored value modified    at most once by the evaluation of an expression.

 

2. Furthermore, the prior value shall be accessed only to determine the value to be stored.

 

                                            ( ISO/IEC C99 reference 6.5 p.67 )  

 

 

원칙 1은 2개의 sequence point 사이에서 하나의 object ( 변수라고 생각 ) 에 대한 side effect 는 최대 1번으로 제한 한다고 해석할 수 있습니다.

따라서  i = i++ ; 은 undefined behavior 입니다. 

i 에 대하여 sequnce point 전에 2번의 side effect 가 있기 때문입니다. 

 

원칙 2는 2개의 sequence point 사이에서 피연산자 A의 연산이 피연산자 B의 연산 결과 영향을 준다면 , A의 연산은 B의 연산에 선행되어야 한다고 해석할 수 있습니다.  

따러서 a[++i] = i ; 은 undefined behavior 입니다. 

할당 연산자 왼쪽 피연산자( a[++i] ) 의 연산 결과는 오른쪽 피연산자( i ) 의 연산 결과에 영향을 주지만 어떤 피연산자 먼저 연산할 지는 unspecified behavior 이기 때문입니다. 

 

 

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

< 연습문제 >

 

다음 statement 가 표준에서 undefined behavior 인지 아닌지 판단하시오.  

undefined behavior 라면 어떤 원칙 때문인지 판단하시오. 

 

(1) i++ * ++i ; 

(2) ++i = 2 ; 

(3) int x = i + i++ ; 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<정답>

 

(1) UB , 1

(2) UB , 1

(3) UB , 2