본문 바로가기

카테고리 없음

undefined behavior ? unspecified behavior ? implementation defined behavior ?

 

C / C++ 에서는 3가지 behavior 를 정하고 이 표현을 적극적으로 활용합니다. 

 

' 이 code 는 unspecified 하다. '  ,

' 이는 implementation - defined 이다. '

 

이런식으로 말입니다.  

 

 

실제 표준 문서에서 쓰인 예시 (ISO/IEC C99 reference 5.2.2)

 

3가지 behavior 의 존재 이유와 각각의 behavior 가 무엇을 의미하는 지 , 그리고 어떤 예시가 있는 지 알아 보겠습니다.

 

 

3가지 behavior 를 정의한 이유 ? 

이를 위해 프로그램의 이식성에 대해 알아야 합니다. 

위키백과 이식성을 검색하면 다음의 내용이 제일 처음 나옵니다. 

 

 

A computer program is said to be portable if there is very low effort required to make it run on different platforms. 


한 플랫폼에서 실행하고 있는 프로그램을 다른 플랫폼에서 실행하려 할 때 , 더 적은 수고가 들수록 그 프로그램은 이식성이 있다고 표현을 합니다.  

 

3가지 behavior 은 프로그램의 이식성을 위해 정의 했습니다.

프로그래머가 작성한 코드가 시스템마다 어떤 동작을 보일 지 안내하기 위해 표준은 3가지 behavior 를 정의하고 문서에서 표현합니다. 

표준 문서를 읽고 내가 작성한 코드가 어떤 behavior 인지 파악한 프로그래머는 

 

' 잘 동작하는 듯 보이지만 내부에서는 꼬였거나 지금 케이스에서만 잘 동작하는 것일 수도 있겠군 '

' 이 컴파일러에서는 이런 동작이 보장되겠군 '

 

등의 판단이 가능합니다. 

  

 

1. undefined behavior

 

undefined behavior 은 해당코드가 어떤 동작을 할지 표준이 보장하지 못합니다. 

잘 동작하는 듯 보여도 내부에서는 꼬여 있을 수도 있고 해당 케이스에서 우연히 동작하는 것일 수도 있습니다.

 

이식성을 해치는 코드 이기 때문에 최대한 지양해야 합니다.

어떤 책에서는 undefined behavior 를 역병(plague)이라고 까지 표현 합니다.

 

undefined behavior 를 마주한 컴파일러는 컴파일을 중단하는 경우도 있지만 C의 관대한 특성에 따라 경고만 주고(혹은 경고도 없이) 컴파일 할 수도 있습니다. 

때문에 내가 작성한 코드가 undefined behavior 인지 아는 것은 매우 매우 중요합니다. 

 

 

gcc 에서 undefined behavior 에대해 경고를 준 모습

 

 

그렇다면 undefined behavior 에는 어떤 것들이 있을까요 ? 

 

기본적으로 쉽게 접할 수 있는 case 로 % 와 / 연산자에서 오른쪽 피연산자가 0 인 경우를 들 수 있습니다. 

 

 

(ISO/IEC C99 reference 6.5.5)

 

 

다음 문서에서 두번째 피연산자가 0 이면 , 그 동작은 undefined 하다고 명시되어 있습니다. 

 

 

다음으로 return type 이 void 가 아닌 non void 함수가 return 문 없이 종료되면 이는 undefined behavior 입니다. 

 

 

GCC C99 버전으로 non void 함수를 return 문 없이 컴파일

 

 

GCC 에서 C99 버전으로 컴파일하면 'control reaches end of non-void function' 이라고 경고 합니다. 

Visual Studio 에서 Microsoft C 에서는 경고없이 컴파일 해 버립니다. ( undefined behavior 를 잘 알고 있어야 하는 이유 )

 

함수에는 main 함수도 포함 입니다. 

다만 C99 부터는 main 함수에서 붙이지 않아도 컴파일러가 알아서 처리해주긴 합니다. 

( 그래도 붙여주는게 컴파일러 입장에서 행복할 껍니다. )  

 

2. unspecified behavior 

 

unspecified behavior 은 표준이 2개 혹은 그 이상의 동작 가능성을 제공하는데 특정 시스템마다 어떤 동작을 할 지 정해서 문서화를 강요하지는 않는 경우를 말합니다. 

undefined behavior 보다는 느슨한 느낌이지만 역시 모르고 쓰면 이식성이 좋지 않은 코드를 작성할 위험이 있습니다.

 

unspecified behavior 에는 피연산자의 계산 순서를 들 수 있습니다. 

 

 

a = 5 ; 
c = (b = a + 2) - (a = 1) ;

 

 

다음의 코드가 실행되면 C 의 값은 6 일까요 2 일까요 ? 

답은 " 이항연산자 '-' 에서 피연산자의 계산 순서는 unspecified behavior 이기 때문에 컴파일러마다 그리고 상황마다 달라질 수 있다. " 입니다.

( 정확히는 A 피연산자의 계산이 B 피연산자의 계산에 영향을 줄 경우 A 피연산자의 계산은 선행되어야 한다는 규칙에 따라 undefined behavior  /  ISO/IEC C99 reference 6.5 )   

다만 논리연산자 (&& , ||) , 조건연산자(3항연산자) , 콤마연산자 는 왼쪽 피연산자 부터 계산 합니다. 

 

 

3. implementation defined behavior 

 

implementation defined behavior 는 unspecified behavior 과 비슷합니다. 

하지만 차이점은 바로 지정이 된다는(specified) 점 입니다.

 

implementation defined behavior 는 특정 시스템마다 표준에서 설명한 동작 가능성 중 어떤 동작을 할 지 정해서 이를 문서화 해야 합니다.  

 

가장 큰 특징은 시스템마다 문서화가 되어 있다는 점 입니다.

 

 

gcc implementation defined behavior document 아래 링크

 

 

대표적인 C compiler 인 gcc 는 아래 링크에서 확인할 수 있습니다.

 

https://gcc.gnu.org/onlinedocs/gcc/C-Implementation.html#C-Implementation

 

 

C Implementation (Using the GNU Compiler Collection (GCC))

4 C Implementation-Defined Behavior A conforming implementation of ISO C is required to document its choice of behavior in each of the areas that are designated “implementation defined”. The following lists all such areas, along with the section number

gcc.gnu.org

 

 

다음은 C89에 microsoft 의 확장 기능을 추가한 Microsoft C 의 implementation defined behavior 입니다.

 

https://learn.microsoft.com/en-us/cpp/c-language/implementation-defined-behavior?redirectedfrom=MSDN&view=msvc-170

 

 

Implementation-Defined Behavior

Learn more about: Implementation-Defined Behavior

learn.microsoft.com

 

implementation defined behavior 코드를 작성할 때는 이런 문서들을 참고하는게 좋습니다. 

 

예시로는 int 자료형의 표현범위를 들 수 있습니다. 

보통 int 자료형의 size 는 4 byte 이므로 표현범위는 ( - 2^31 ~ 2^31 - 1 ) 라고 생각하는데 

이는 64-bit machine 일때만 그렇습니다. 

 

C표준은 다음의 2가지 규칙만을 제시했습니다. 

첫째 , short int , int , long int 자료형을 필요로 하고 각각은 특정 범위를 커버할 것. 

둘째 , int 표현범위가 short int 의 표현범위보다 좁지 않고 long int 의 표현범위가 int 의 표현범위보다 좁지 않을 것.

 

 

sizes in 16 bit machine

 

 

위 표에서 보이듯 16 bit machine 에서 integer type 은 2 byte size , ( - 2^15 ~ 2^15 - 1 ) 표현범위 입니다. 

 

 

<stdint.h> 중 일부

 

 

프로그램의 이식성을 위해 typedef 를 이용하여 int8_t , int16_t , int32_t , int64_t 를 쓸 수도 있습니다.

각각에 해당하는 bit size 라고 생각하고 쓰다가 시스템이 달라졌을 때 , typedef 부분만 바꾸면 그대로 쓸 수 있습니다.