Creative Commons License

Microsoft .NET

닷넷!시작하기
닷넷! Ver 2.0~
닷넷!스킬업
웹개발
윈폼개발
실용모듈개발
Tip & Tech
하루 한 문법

Microsoft .NET 개발자들을 위한 공간입니다. 기초강의에서 부터 고급 기술 정보 및 팁등을 다루도록 하겠습니다.

.

닷넷!시작하기

Microsoft. NET 을 시작하는 분들을 위한 강좌입니다. 주로 기초적인 내용과 때론 기본적인 내용을 다룹니다

[C# 기초강좌] 20. C# Checked / Unchecked

작성자 : 박종명
최초 작성일 : 2010-07-29 (목요일)
최종 수정일 : 2010-07-29 (목요일)
조회 수 : 10369

정수의 산술연산 오버플로(overflow) checked 로 검사합니다

 

안녕하세요. 박종명입니다. 닷넷 스무 번째 강좌를 진행하도록 하겠습니다

근래 일이 좀 바빠서 강좌 업데이트가 늦어졌네요. ^^;

 

이번 강좌에서 checked unchecked 에 대해 알아보도록 하겠습니다

역시 내용이 간단하니 가볍게 읽어 주세요

 

 

 

오버플로(Overflow)

Overflow 의 사전적 의미는 넘치다’, ‘범람하다 입니다

 

프로그래밍에서도 동일한 의미로 사용됩니다. 즉 정해진 메모리 크기의 최대치를 오버(over)해서 값을

저장하려는 경우 오버플로가 발생한다고 말합니다

 

byte, int 와 같은 기본형식 정수 타입은 각각 그 메모리의 크기가 정해져 있습니다

예를 들어 byte의 경우 부호 없는 8bit 정수이기에 0 ~ 255까지 표현 가능합니다

다시 말해 byte에 저장할 수 있는 최대값은 255라는 것이죠.

만일 byte 256 이상의 값을 대입하면 어떻게 될까요? 일단 최대값을 오버했으니 오버플로입니다.

 

닷넷 실행환경에서 이러한 오버플로를 어떻게 처리할까요?

상황적으로는 오류에 가까운 상황이죠. 그렇다면 예외(Exception)를 던져 줄까요? 답은 아닙니다.

 

기본적으로 닷넷 실행환경은 오버플로 예외를 발생시키지 않게 설정되어 있습니다

대신 MSB(최상위비트)가 무시되어 이상한 값이 나오거나 한계치가 넘은 값이 초기 값으로 순환될 뿐이죠

 

뭔가 이상하죠? 오류에 가까운 이 상황이 예외가 아니라는 것이 말이죠

그러나 길은 있습니다. 바로 checked 입니다. 이 키워드로 오버플로 발생을 예외로 보고받을 수 있게 됩니다

 

 

Checked / Unchecked

이 키워드는 매우 직관적입니다. 말 그대로 (오버플로를) 검사할지 말지를 결정하는 키워드입니다

비주얼스튜디오를 열어서 아래와 같이 코딩 해보겠습니다

 

static void Main(string[] args){

    byte b = byte.MaxValue; //byte의 최대값 255

    b = (byte) (b + 100); //오버플로 발생

    Console.WriteLine(b); //결과는 99

}

 

byte 타입 b 변수에 최대값을 오버해서 값을 대입시켜 봤습니다

byte는 최대 255 까지 표현 가능함으로 355(255 + 100) 은 오버플로죠

 

분명 문제가 있는 상황이지만 예외는 발생하지 않습니다. 대신 기대하지 않았던 99라는 값이 저장됩니다

이 상황이 Unchecked 상황입니다. 즉 산술 연산의 오버플로를 검사하지 않는 상황입니다

닷넷 실행환경인 CLR은 기본적으로 정수 산술연산에서 오버플로 검사를 수행하지 않습니다

 

이제 CLR에게 오버플로를 검사하도록 코드를 수정해 보겠습니다

 

byte b = byte.MaxValue; //byte의 최대값 255

 

checked

{

  b = (byte)(b + 100); //오버플로 발생. System.OverflowException 예외 발생

}

 

오버플로가 발생할만한 위치를 checked 블록으로 감싸 버렸습니다

이제 실행시켜보면 예외가 발생하면서 프로그램이 멈추게 됩니다. 그럼 예외처리를 해 볼까요?

 

byte b = byte.MaxValue; //byte의 최대값 255

 

try

{

      checked

      {

           b = (byte)(b + 100); //오버플로 발생. System.OverflowException 예외 발생

      }

}

catch (System.OverflowException ex)

{

       Console.WriteLine(ex.Message);

}

 

Console.WriteLine(b); //b 값은 예외발생 전 값인 255가 유지됨

 

이제 오버플로 상황에서 예외가 발생되며 이 예외를 잡아서 처리할 수 있게 되었습니다

그리고 오버플로 결과 예외이기 때문에 변수 b에 반영되지 않습니다

 

그럼 Unchecked 키워드는 언제 사용할까요?

기본적으로 닷넷 실행환경은 Unchecked 입니다. 따라서 오버플로를 검사하지 않을 경우 Unchecked를 굳이

명시하지 않아도 됩니다. 즉 다음 두 코드는 동일한 코드입니다

 

코드 1>

b = (byte)(b + 100); //오버플로 발생

 

코드2>

unchecked

{

     b = (byte)(b + 100); //오버플로 발생

}

 

Unchecked 키워드는 전역적으로 checked 환경이 설정되었을 특정 영역만 Unchecked 하고 싶을 경우

사용할 수 있습니다

 

 

Checked / Unchecked 적용 범위

Checked, Unchecked 하나의 연산 또는 하나의 블록 그리고 전역적으로 설정할 수 있습니다

앞서 살펴본 예제에서는 checked 블록을 이용했습니다

그러나 다음과 같이 하나의 연산으로도 한정할 수 있습니다

.

b = checked((byte) (b + 100)); 또는 b = unchecked((byte) (b + 100));

 

그리고 마지막으로 프로그램 전역적으로 설정할 수 있습니다

비주얼스큐디오의 프로젝트 속성 -> 빌드 -> 고급창에서 다음과 같이 설정 가능합니다

 

 

 

이렇게 전역적으로 오버플로 검사를 설정하게 되면 프로그램의 모든 정수 연산에 검사가 수행됩니다

앞서 Unchecked 키워드를 언제 사용할지 물어보았는데요.

바로 이런 상황에서 명시적으로 unchecked를 이용하면 되는 것입니다.

즉 프로그램의 전체 영역이 checked 환경이기 때문에 특정 영역만 Unchecked 하고 싶을 경우 사용하면 되는 것이죠

 

 

문제는 개발자들이 잊을 수 있다는 것입니다

닷넷 실행환경이 기본적으로 오버플로를 검사하지 않는 환경(Unchecked 환경)이라고 했습니다

간혹 이런 기본 설정이 개발자를 무감각(?)하게 만드는 요인이 될 수 있습니다

 

정수 연산의 오버플로 상황은 경우에 따라서는 매우 민감한 사안일 수 있습니다

프로그램의 성격에 따라 다르겠지만 정수 연산의 값 초과 상황이 무시되는 것은 바람직하지 않다는 거죠

게다가 단순 무시가 아니라 엉뚱한 값으로 변경되기 때문에 문제는 더욱 심각해 질 수 있습니다

(적어도 그 프로그램이 정수 연산을 매우 정확하게 해야 하는 경우에는요)

 

오버플로 상황을 검사하지 않으면 수행 속도는 더 빠를 수 있습니다

그러나 결과는 예측하지 못하게 되는 겁니다. 늘 얻는 것이 있으면 잃는 것도 있기 마련이죠

 

제가 지적하고 싶은 것은,

툴이 기본 설정으로 오버플로를 검사를 수행하지 않게 됨으로써 개발자들도 오버플로 검사를 잊을 수 있다는 것입니다.

 

무슨 말인고 하니, 산술 연산에서 오버플로가 발생할 수 있고 경우에 따라 프로그램이 엉뚱하게 동작할 수 있다는

사전 지식을 가진 개발자라 할지라도 툴의 환경에 익숙해져 무감각해 진다는 것이죠

 

실제로 실무의 많은 코드에서도 정수 오버플로 검사를 수행하지 않는 것을 많이 발견하게 됩니다

그러나 이것은 프로그램이 unchecked 환경에서 수행되어야 하기 때문이 아니고 검사 수행을 잊어먹은 것이 대부분입니다. 그리고 운 좋게(?) 오버플로 상황은 발생하지 않거나 적당히 발생해도 무관한 프로그램 환경이라서 결국 인지도 못하게 됩니다. 이 상황의 반복으로 개발자는 영원히(?) 오버플로 검사를 잊게 됩니다.

 

조금 과장이지만, 많은 경우 오버플로 검사 수행을 하고 있지 않습니다. 그리고 개발자도 잊고 있구요.

툴의 기본 설정이 개발자의 조심성을 저해하는 대표적인 사례라고 생각합니다

 

자신의 응용프로그램 특징을 파악하세요. 그리고 정수 연산 오버플로 검사가 필요한지 아닌지 판단하세요

그리고 상황에 맞도록 최대한 성능 저해 없이 적용하길 권장 드립니다(범위 별 적용이 가능하잖아요)

 

 

 

형 변환과 오버플로

정수의 산술 연산뿐만 아니라 정수 계열 형식간 형 변환 시에도 오버플로가 발생할 수 있으며

역시 checked / unchecked 로 제어할 수 있습니다

 

다음 코드는 int byte로 형 변환 하면서 오버플로 검사를 수행하도록 했습니다

 

int i = 256;

 

checked

{

    byte s = (byte) (i); //int byte로 형변환(오버플로)

      Console.WriteLine(s);

}

 

 

 

IL 코드

지금까지 살펴 본 내용만으로도 기본 지식은 다 배운 겁니다.

이후 진행되는 내용은 조금 더 깊게 그리고 조금 더 참고적으로 알아볼 내용들입니다

 

닷넷 실행환경인 CLR은 산술 연산을 위한 IL 명령어를 미리 정의해 두었습니다

덧넷은 add, 곱셈은 mul 과 같은 식이죠. 그런데 이런 산술 연산은 두 가지 버전으로 존재합니다.

오버플로 검사용과 비 검사용이죠.

즉 우리의 checked / unchecked 설정으로 각 환경에 맞는 IL명령어가 수행되는 것입니다

 

덧셈을 예를 들어보면, 다음과 같이 구분된 IL 명령어가 존재합니다

add: 오버플로 검사를 수행하지 않는 IL 명령어 (unchecked 환경)

add.ovf: 오버플로 검사를 수행하는 IL 명령어 (checked 환경)

 

간단한 코드를 작성해 보고 그 코드에 대한 IL 코드를 보겠습니다

다음 코드는 두 정수에 대한 덧셈을 수행하는데 checked unchecked 환경에서 각각 수행되도록 하였습니다

 

int i = 1, j = 2;

 

unchecked

{

    int k = i + j;

}

checked

{

    int k = i + j;

}

 

그리고 IL 코드를 보겠습니다

 

 

앞서 설명한대로 오버플로 검사 시에는 add.ovf, 비 검사 시에는 add 명령어가 각각 수행되는 것을 확인할 수 있습니다.

 

결국 checked / unchecked 설정은 IL 코드의 어떤 명령어를 수행할지를 결정하는 것이며

xxx.ovf 명령어는 오버플로 상황을 검사한다는 것입니다

 

다음 MSIL(Microsoft Intermediate Language) 명령은 OverflowException throw합니다(by msdn)

add.ovf.<signed>

conv.ovf.<to type>

conv.ovf.<to type>.un

mul.ovf.<type>

sub.ovf.<type>

newarr

 

 

비트 연산자, 쉬프트 연산자는 오버플로를 일으키지 않습니다

정수 계열의 비트, 쉬프트 연산자는 오버플로를 발생하지 않습니다

 

한가지 예를 들어 보겠습니다

정수에 왼쪽 쉬피트 연산(<<) 을 수행하면 주어진 값의 배수가 됩니다

즉 다음과 같이 왼쪽으로 한 칸 쉬프트 하면 (곱하기 2) 한 것과 같습니다

 

int i = 10;

i = i << 1; //결과: 20

 

그럼 checked 환경에서 int 값을 초과하도록 쉬프트 연산을 하게 되면 어떨까요?

 

int i = Int32.MaxValue;

 

checked

{

     i = i << 1;

}

 

Console.WriteLine(i); //결과: -2

 

이 코드는 예외를 발생하지 않습니다. 그리고 결과는 엉뚱한 값이죠

만일 checked 블록에 i = i * 2 로 곱셈 연산을 했다면 OverflowException 이 발생하는 것과 대조적이죠

(결과가 -2가 나왔는데요. 이것은 unchecked i = i * 2 를 한 것과 동일한 결과입니다)

 

결국 산술연산 이외에 비트 연산, 쉬프트 연산은 오버플러는 검사하지 않는 다는 것이며 checked 설정과 무관하다는 것입니다

 

 

 

중첩된 함수와 오버플로

다음 코드를 보겠습니다. Main 함수에서 Sum 함수를 호출하는 구조입니다

Sum 함수는 정수를 합하여 반환합니다. 이 때 Sum함수에서 checked 를 설정했습니다

 

static void Main(string[] args){

    int i = Int32.MaxValue;

    int j = Int32.MaxValue;

    int k = Sum(i, j);

    Console.WriteLine(k);

}

 

static int Sum(int i, int j){

     int k = 0;

     checked

     {

         k = i + j;

     }

    return k;

}

 

결과는 당연히 OverflowException 예외가 발생합니다. Checked 했으니깐요

 

그렇다면 이제 코드를 변경해 보겠습니다

Checked Sum 함수가 아닌 이를 호출하는 Main 함수에서 설정해 보겠습니다

 

static void Main(string[] args){

    int i = Int32.MaxValue;

    int j = Int32.MaxValue;

 

    int k = 0;

    checked

    {

         k = Sum(i, j);

    }

    Console.WriteLine(k);

}

 

static int Sum(int i, int j){          

    int k = i + j;

    return k;

}

 

결과는? -2가 반환됩니다. checked 설정을 했는데도 예외가 발생하지 않고 엉뚱한 값이 나왔습니다

왜 그럴까요?

 

checked 설정 위치 때문에 그렇습니다

앞서 checked 를 설정하면 산술 연산에 대한 오버플로 검사 버전 IL 코드로 대치된다고 하였습니다

이 때 산술연산이 발생하는 바로 그 위치에서 checked 설정에 의해 변환되는 것이죠

따라서 두 번째 예와 같이 산술연산이 수행되는 영역(Sum)이 아닌 바깥 영역(Main)에서 checked 가 설정되어 무의미해진 것입니다. Main 함수에는 Sum을 호출할 뿐이지 산술연산을 직접 처리하지는 않으니깐요

 

 

정수 이외의 숫자 자료형과 오버플로

Checked / Unchecked 설정은 정수형 자료에 대한 오버플로 검사 수행여부를 결정하는 키워드입니다

부동 소수점 형식은 무한 값(infinity) NaN(Not a Number) 을 표현하는 규정이 있기 때문에 산술 연산 오버플로가

발생하지 않습니다. 다시 말해 오버플로라는 상황자체가 무의미할 수 있으며 checked 옵션을 설정해도 오버플로 예외는

발생하지 않는다는 것입니다

 

반면 고정밀도 부동소수점 형식인 Decimal unchecked 로 설정하더라도 항상 오버플로 상황에 예외를 발생 시킵니다. Decimal 타입은 독특한 형식으로 산술 연산에 대한 IL 명령은 존재하지 않으며

Decimal 타입 자체의 메서드에서 처리하도록 되어 있습니다. 이 메서드에서는 checked / unchecked 설정과는 무관하게

자체적으로 항상 오버플러 검사를 수행하도록 되어 있습니다

 

참고로 Decimal (달러 단위) 화폐 계산과 같이 숫자가 매우 정확해야 하는 회계 프로그램에서 자주 사용되는 자료형입니다.

이런 프로그램들은 오버플로로 인한 엉뚱한 결과는 용납될 수 없는 환경이죠

Decimal unchecked 상황과 무관하게 항상 오버플로를 검사한다는 것은 어찌 보면 당연한 것이지 싶습니다

 

이상 checked unchecked 에 대해 알아보았습니다

그럼. 남은 한 주 마무리 잘 하시고 좋은 주말 되세요~~

∵Commented by 뱀병장 at 2010-08-11 오전 10:26:32  
오랜만에 인사드립니다!!
바쁘시더라도 지속적으로 강좌 올려주세요!!!!! ㅎㅎ
담에 쇠주 한 잔 하시져~
∵Commented by 박종명 at 2010-08-20 오후 1:04:44  
오.. 새신랑, 댁용군~
명텐도는 잘 되가남? 결혼생활은 어뗘? 집뜰이도 안하고 말이야..
조만간 한잔 혀~
이름
비밀번호
홈페이지
UT <- 왼쪽의 문자를 오른쪽 박스에 똑같이 입력해 주세요