Post List

2018/01/05

C# __6.0 예약어_Checked_Params_Unsafe__Keyword

checked 예약어
 - 정수 계열 타입의 산술 연산을 하거나, 서로 다른 정수 타입 간의 형변환을 하게 되면 표현 가능한 숫자의 범위를 넘어서는 경우가 발생합니다.
예를 들어, 2바이트 정수인 short타입은 -32,768 ~ 32,767사이의 값을 표현할 수 있는데 그 수를 넘어서는 상황이 되면 오버플로 또는 언더플로 현상이 발생하게됩니다.

개발자는 연산식에서 오버플로나 언더플로가 발생한 경우 C#으로 하여금 오류를 발생시키라고 명시할 수 있는데, 이때 checked 예약어가 그와 같은 역할을 합니다.

1
2
3
4
5
6
7
short c = -32768;
c--;
Console.WriteLine(c); // 출력결과 32767
int n = -32799;
= (short)n;
Console.WriteLine(c); // 출력결과 32767
cs
 * 오버플로(overflow) : 데이터가 상한값을 넘어 하한값으로 돌아가는 것
 * 언더플로(underflow) : 오버플로와 반대 현상

1
2
3
4
5
6
7
short     c = 32767;
int      n = 32768;
checked
{
    c++// 예외발생
}
cs
* C#컴파일러 수준에서 checked 상황을 전체 소스코드에 걸쳐 강제로 적용할 수 있는 옵션을 제공합니다. 
 - 비쥬얼스튜디오에서 프로젝트 속성 창의 "빌드"탭을 선택한 후 "고급..." 버튼을 누르면 "산술 연산 오버플로/언더플로 확인" 옵션이 나타납니다.

1
2
3
4
5
6
short     c = 32767;
unchecked
{
    c++// 오류가 발생하지 않는다.
}
cs
* /checked 옵션과 함께 컴파일된 경우, 반대로 특정 영역의 산술 연산에 대해서는 오버플로나 언더플로가 발생해도 오류를 내지 말라고
개발자가 unchecked예약어를 지정할 수 있습니다.


params 
 - 매개변수의 갯수가 확실하지 않을 경우 입력받을 인자의 타입에 해당하는 배열로 선언한 다음 params 예약어를 붙입니다. 만약 입력타입을 지정할 수 없다면 object를 사용할 수도있습니다.

unsafe
 - C#의 독특한 특징 중 하나는 기존 네이티브 C/C++ 언어와의 호환성을 위한 기능이 추가됐다는 점입니다. WinAPI를 직접 호출할 수 있는 extern 예약어도 그 사례이며, C++와의 호환성을 높이기 위해 존재하는 또 한가지 사례가 바로 안전하지 않은 컨텍스트에 대한 지원이랍니다. ( * 안전하지 않는 코드란 포인터를 사용하는 것을 의미한다. )
   C#은 C/C++의 포인터를 지원하며 unsafe 예약어는 포인터를 쓰는 코드를 포함하는 클래스나 그것의 멤버 또는 블록에 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void UseParams(params int[] list)
{
    for (int i = 0; i < list.Length; i++)
    {
        Console.Write(list[i] + " ");
    }
    Console.WriteLine();
}
public static void UseParams2(params object[] list)
{
    for (int i = 0; i < list.Length; i++)
    {
        Console.Write(list[i] + " ");
    }
    Console.WriteLine();
}
cs

fixed
 - 참조 형식의 데이터는 직접적인 포인터 연산을 지원할 수 없습니다. 참조 형식의 인스턴스는 힙에 할당되고 그 데이터는 가비지컬렉터가 동작할 때마다 위치가 바뀔 수 있기 때문입니다. 이로 인해 포인터를 이용해 그 위치를 가리키면 가비지 컬랙터 이후 엉뚱한 메모리를 가리킬 수 있다는 위험이 따릅니다. 
바로 이러한 문제를 해결하기 위해 C#에는 fixed라는 예약어를 도입했습니다. fixed예약어는 힙에 할당된 참조 형식의 인스턴스를 
가비지 컬렉터가 움직이지 못하도록 고정함으로써 포인터가 가리키는 메모리를 유효하게 유지시킵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Managed
{
    public int count;
}
class Program
{
    unsafe static void Main(string[] args)
    {
        Managed inst = new Managed();
        
        inst.count = 5;
        fixed(int* pValue = &inst.count)
        {
            *pValue = 6;
        }
    
    }
}
cs
 * 여기서 Managed 타입의 객체인 inst 변수에 대해 직접 포인터를 가져오지 않다는점에 유의해야합니다.
 C#은 객체 인스턴스의 포인터를 가져오는 것을 허용하지 않기 때문입니다. 값 형식이거나 값 형식의 배열인 경우에만 포인터 연산이 가능하답니다!.
하지만 fixed되는 대상은 객체의 데이터를 포함한 객체가 됩니다. 따라서 보통 fixed된 포인터는 관리 프로그램의 힙에 할당된 데이터를 관리되지 않은 프로그램에 넘기는 용도로 사용됩니다.

stackalloc
 - 값 형식은 스택에 할당되고 참조 형식은 힙에 할당됩니다. 그런데 값 형식임에도 그것이 배열로 선언되면 힙에 할당됩니다. stackalloc 예약어는 값 형식의 배열을 힙이 아닌 스택에 할당하게 해줍니다.
1
int* Array = stackalloc int[1024]; // int 4byte * 1024 = 4KB 용량을 스택에
cs

* 포인터 연산을 사용하기 때문에 stackalloc도 unsafe 문맥에서만 사용해야 합니다.

Q. 여기서! 왜 배열을 스택에 만들고 싶은 이유는?
a. 힙을 사용하지 않으므로 가비지 컬랙터의 부하가 없다는 장점 때문입니다. 가비지 컬랙터의 호출 빈도를 조금이라도 낮출 수 있어 좀 더 원활한 게임 실행이 가능해지기 때문이죠.

Q. 그럼 반대로 왜 스택에 배열을 만들고 싶지 않을까?
a. 스택은 스레드마다 할당되는 메모리로 윈도우의 경우 기본값으로 1MB 규모의 크기를 갖습니다. 이처럼 제한된 자원을 남용하면 자칫 프로그램의 실행에 오류를 발생시킬 수 있으므로 사용할 떄 신중해야합니다. 때문에 일부 특수한 용도를 제외하고는 stackalloc예약어가 사용되는 경우는 거의 없답니다.

그 외 키워드
volatile/ lock/ internal/ try, catch, throw/ finally/ using