언어/C#

C# 의 타입 - 2. 기본 타입 (Primitve Type)

tsyang 2020. 10. 31. 20:07

기본 타입 (Primitve Type)


32비트의 정수형 변수를 할당한다면 다음과 같이 코드를 쓸 수 있다.

 

System.Int32 a = new System.Int32();

 

그런데 이렇게 쓰면 너무 귀찮으니까... 대부분의 컴파일러는 다음과 같이 표현하도록 해준다. 

 

int a = 0;

 

그리고 위와 같은 코드는 System.Int32 타입을 사용한 코드와 의미가 동일한 IL 코드를 만들어준다. 이처럼 컴파일러가 직접 지원하는 데이터 타입들을 기본 타입이라고 부른다.

 

C#의 기본 타입과 FCL에 대응되는 타입은 다음과 같다.

 

기본 타입 FCL 타입 CLS 호환 비고
sbyte System.SBtye ㄴㄴ  
byte System.Byte ㅇㅇ  
short System.Int16 ㅇㅇ  
ushort System.UInt16 ㄴㄴ  
int System.Int32 ㅇㅇ  
uint System.UInt32 ㄴㄴ  
long System.Int64 ㅇㅇ  
ulong System.UInt64 ㄴㄴ  
char System.Char ㅇㅇ 16비트임, C++에서 쓰는 8비트 아님
float System.Single ㅇㅇ  
double System.Double ㅇㅇ  
bool System.Boolean ㅇㅇ  
decimal System.Decimal ㅇㅇ  
string System.String ㅇㅇ  
object System.Object ㅇㅇ  
dynamic System.Object ㅇㅇ FCL타입은 System.Object임에 주목

 

참고 

FCL (Framework Class Library) : FCL + CLR 이 닷넷 프레임워크

CLS (Common Language Specification) : 닷넷 컴파일러가 지원해야 하는 표준 규약, 닷넷 프레임웍 언어가 지원해야 하는 언어 규약, 이걸 지키면 닷넷 프레임워크에서 실행 가능하며 CLR을 통해 실행됨. 

 

주의사항

  • 운영체제가 32비트 64비트라고 int가 32비트 64비트로 바뀌는 게 아니다. int는 그냥 System.Int32임 
  • 다른 언어에서는 long이 16비트 혹은 32비트 부호있는 정수인 경우가 있으니 주의해야 함 (C#에서는 64비트)
  • FCL이름을 어느정도 알아두는 게 편하다. 가령 System.Convert 타입의 경우 ToSingle 이런 게 있는데 single이 float과 대응된다는 사실을 모르면 좀 혼동스러울 것이다.
  • 다른 언어도 CLR을 이용할 수 있는데, FLC의 경우 거의 C#용도로만 짜였기 때문에 Array.GetLongLength같은 경우 사용하는 언어마다 뭘 반환하는지 헷갈릴 수 있다.

따라서 

Int32 a = 5;
Int64 b = a;

이런식으로 FCL의 정확한 타입 명칭을 사용하는 것도 고려해볼 만하다. 

 

 

 

오버플로우


프로그래밍 언어마다 오버플로우를 처리하는 방법이 다르다. C/C++은 오버플로우를 오류로 생각하지 않는다. 비주얼 베이직 닷넷의 경우 이를 오류로 취급하며 프로그램의 실행을 중단시킨다.

 

CLR의 경우 기본적으로 오버플로우에 대한 검사를 수행하지 않는다. 예를 들어, CLR의 IL 명령어는 add를 통해 두 값을 더하며 오버플로우에 대한 검사를 수행하지 않는다. 이때, add.ovf 라는 명령을 이용하면 두 값을 더하는 도중 오버플로우가 발생할 경우 System.OverflowException예외를 발생시킨다. 마찬가지로 mul, sub, conv역시 .ovf를 붙여 오버플로우를 검사하게 할 수 있다.

 

오버플로우에 대한 검사를 수행하지 않으므로써, 프로그램은 좀 더 빠르게 동작한다. 그 대신 개발자들은 오버플로우가 발생하지 않도록 코드를 설계해야 한다.

 

C#컴파일러가 오버플로우 검사를 수행할 수 있는 IL코드를 만들게 하기 위해서는 컴파일러 스위치 중 /checked+를 지정해야 한다. 이 스위치는 컴파일러가 add, sub, mul, conv 등의 IL 명령을 .ovf명령으로 바꾼다. (대신 프로그램은 조금 느려진다.)  스위치는 다음과 같이 checked/ unchecked 연산자를 통해 설정할 수 있다.

 

UInt32 a = unchecked((UInt32)(-1)); // 문제 없음
Byte b = 100;
b = checked((Byte) (b + 200)); // System.OverflowException 발생

 

이 두 연산자는 다음처럼 블록으로도 쓸 수 있다.

 

checked 
{
 Byte b = 100;
 b += 200;
}

단, 중간에 메서드를 호출하는 경우에는 메서드 내부에서 또 스위치를 지정해주어야 한다.

 

checked {
	Byte a = 200;
    Byte b = 200;
    Add(a,b); // 메서드 내부에서 checked 안해주면 OverflowException 발생하지 않음
}

 

 

오버플로우와 관련하여 고려해볼만한 내용들

 

  • UInt32, UInt64같은 부호 없는 데이터보다는 부호 있는 데이터를 사용하는 것을 고려할만하다. 이렇게 하면 오버플로우나 언더플로우에 대한 검사를 더 잘 수행할 수 있다. 또한 클래스 라이브러리의 여러 부분들은 부호 있는 값을 하드코딩된 형태로 반환하고 있으며 이런 것들을 캐스팅 없이 활용하는 것이 좋다. 그리고 애초에 부호 없는 숫자 타입은 CLS호환 사양이 아니다.
  • 명시적으로 checked나 unchecked 블록을 사용하여 오버플로우가 발생할 수 있거나 없는 부분(필요 없는 부분)을 지정해주면 좋다.
  • checked나 unchecked 키워드를 사용하지 않는 경우에는 직접 오버플로우에 대한 검사를 수행하고 필요한 경우 예외를 발생하도록 하는게 좋다.

참고로 checked, unchecked 연산자 말고도 IDE에서 일괄적으로 /checked 스위치를 지정해 줄 수 있다.