C# 안전하지 않은(Unsafe) 코드 마스터하기: 포인터와 메모리 관리 가이드
C#은 unsafe 수정자로 표시된 코드 블록이나 함수에서 포인터 변수 사용을 허용합니다. 안전하지 않은 코드(unsafe code) 또는 비관리 코드는 포인터(pointer) 변수를 사용하는 코드 블록입니다.
포인터
포인터는 다른 변수의 주소, 즉 메모리 위치의 직접 주소를 값으로 가지는 변수입니다. 모든 변수나 상수와 마찬가지로 포인터를 사용하기 전에 선언해야 합니다.
포인터 선언의 일반적인 형식은 다음과 같습니다.
type *var-name;
다음은 유효한 포인터 선언들입니다.
int *ip; /* 정수 포인터 */
double *dp; /* double 포인터 */
float *fp; /* float 포인터 */
char *ch; /* 문자 포인터 */
다음 예제는 unsafe 수정자를 사용하여 C#에서 포인터 사용을 보여줍니다.
using System;
namespace UnsafeCodeApplication {
class Program {
static unsafe void Main(string[] args) {
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} ", var);
Console.WriteLine("Address is: {0}", (int)p);
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
Data is: 20
Address is: 99215364
전체 메서드를 unsafe로 선언하는 대신 코드의 일부만 unsafe로 선언할 수도 있습니다. 다음 섹션의 예제가 이를 보여줍니다.
포인터를 사용하여 데이터 값 검색
포인터 변수가 참조하는 위치에 저장된 데이터는 ToString() 메서드를 사용하여 검색할 수 있습니다. 다음 예제는 이를 보여줍니다.
using System;
namespace UnsafeCodeApplication {
class Program {
public static void Main() {
unsafe {
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} ", var);
Console.WriteLine("Data is: {0} ", p->ToString());
Console.WriteLine("Address is: {0} ", (int)p);
}
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
Data is: 20
Data is: 20
Address is: 77128984
메서드에 포인터를 매개 변수로 전달
포인터 변수를 메서드의 매개 변수로 전달할 수 있습니다. 다음 예제는 이를 보여줍니다.
using System;
namespace UnsafeCodeApplication {
class TestPointer {
public unsafe void swap(int* p, int *q) {
int temp = *p;
*p = *q;
*q = temp;
}
public unsafe static void Main() {
TestPointer p = new TestPointer();
int var1 = 10;
int var2 = 20;
int* x = &var1;
int* y = &var2;
Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
p.swap(x, y);
Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10
포인터를 사용하여 배열 요소 접근
C#에서 배열 이름과 배열 데이터와 동일한 데이터 형식의 포인터는 같은 변수 형식이 아닙니다. 예를 들어 int *p와 int[] p는 같은 형식이 아닙니다. 포인터 변수 p는 메모리에서 고정되어 있지 않으므로 증가시킬 수 있지만, 배열 주소는 메모리에서 고정되어 있어 증가시킬 수 없습니다.
따라서 C 또는 C++에서 전통적으로 하는 것처럼 포인터 변수를 사용하여 배열 데이터에 접근해야 하는 경우(참조: C 포인터), fixed 키워드를 사용하여 포인터를 고정해야 합니다.
다음 예제는 이를 보여줍니다.
using System;
namespace UnsafeCodeApplication {
class TestPointer {
public unsafe static void Main() {
int[] list = {10, 100, 200};
fixed(int *ptr = list) /* 포인터에 배열 주소 할당 */
for (int i = 0; i < 3; i++) {
Console.WriteLine("Address of list[{0}]={1}", i, (int)(ptr + i));
Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
}
Console.ReadKey();
}
}
}
위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.
Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200
안전하지 않은 코드 컴파일
안전하지 않은 코드를 컴파일하려면 명령줄 컴파일러에 /unsafe 명령줄 스위치를 지정해야 합니다.
예를 들어, 안전하지 않은 코드를 포함하는 prog1.cs 프로그램을 명령줄에서 컴파일하려면 다음 명령을 사용합니다.
csc /unsafe prog1.cs
Visual Studio IDE를 사용하는 경우 프로젝트 속성에서 안전하지 않은 코드 사용을 활성화해야 합니다.
방법은 다음과 같습니다.