C#의 포인터 기능은 메모리 직접 조작이나 네이티브 코드와의 상호작용이 필요한 고수준 기능입니다. 이 기능은 안전하지 않은 컨텍스트에서 사용되며, 값 형식(예: int, double, struct)과 함께 사용됩니다. 포인터는 CLR(공동 언어 런타임)의 일부 보안 검사를 우회할 수 있어 주의 깊은 사용이 필요합니다.
1. 안전하지 않은 코드 활성화
포인터를 사용하려면 프로젝트에서 안전하지 않은 코드를 활성화해야 합니다. 프로젝트 속성에서 "안전하지 않은 코드 허용" 옵션을 선택하거나, 컴파일 시 /unsafe 플래그를 사용합니다.
2. 안전하지 않은 컨텍스트
포인터는 unsafe 키워드로 표시된 코드 블록에서만 사용할 수 있습니다. 이는 메서드, 속성 또는 일반 코드 블록일 수 있습니다.
3. 포인터 기본 개념
포인터는 메모리 주소를 저장하는 변수입니다. C#에서 포인터 타입은 타입 뒤에 *를 붙여 선언합니다. 예를 들어 int*는 정수를 가리키는 포인터입니다.
4. 포인터 연산자
- &: 변수의 주소를 가져오는 연산자
- \*: 포인터가 가리키는 메모리 위치의 값을 접근하는 연산자
- ->: 구조체 멤버에 포인터를 통해 접근하는 연산자 (예: (*p).member)
5. 배열과의 연관성
포인터는 배열에 접근할 수 있으며, 포인터 연산을 통해 배열을 순회할 수 있습니다.
6. fixed 문
가비지 컬렉터가 관리 메모리에서 객체를 이동할 수 있으므로, 배열이나 문자열과 같은 관리 변수에 포인터로 접근할 때 fixed 문을 사용하여 변수의 위치를 고정해야 합니다.
7. 예제 코드
예제 1: 기본 포인터 작업
using System;
class Program
{
static unsafe void Main()
{
int data = 10;
int* ptr = &data;
Console.WriteLine($"데이터: {data}");
Console.WriteLine($"주소: {(int)ptr}");
Console.WriteLine($"포인터로 접근: {*ptr}");
*ptr = 20;
Console.WriteLine($"변경된 데이터: {data}");
}
}
예제 2: 배열과 포인터 연산
using System;
class Program
{
static unsafe void Main()
{
int[] arr = { 1, 2, 3, 4, 5 };
fixed (int* ptr = arr)
{
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine(*(ptr + i));
}
}
}
}
예제 3: 구조체 조작
using System;
struct Coordinate
{
public int X;
public int Y;
}
class Program
{
static unsafe void Main()
{
Coordinate coord;
coord.X = 10;
coord.Y = 20;
Coordinate* p = &coord;
Console.WriteLine($"({p->X}, {p->Y})");
Console.WriteLine($"({(*p).X}, {(*p).Y})");
}
}
8. 스택 할당
stackalloc 키워드를 사용하여 스택에 메모리를 할당할 수 있습니다. 이 방식은 fixed 문을 사용할 필요 없으며, 가비지 컬렉션의 영향을 받지 않습니다.
using System;
class Program
{
static unsafe void Main()
{
int* stackMem = stackalloc int[5];
for (int i = 0; i < 5; i++)
{
stackMem[i] = i * i;
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(stackMem[i]);
}
}
}
9. 주의사항
- 포인터 코드는 반드시 unsafe로 표시해야 합니다.
- 참조 형식(예: 클래스 인스턴스)은 fixed 문 내에서만 포인터로 접근 가능합니다.
- 포인터 연산은 동일한 타입의 포인터에만 적용 가능합니다.
- 잘못된 메모리 접근은 프로그램 충돌을 유발할 수 있습니다.
10. 사용 시나리오
- 배열 경계 검사 제거를 통한 성능 최적화
- C/C++ 라이브러리와의 상호작용
- 대규모 데이터 처리(예: 이미지 처리)
11. 보안 및 최적화 팁
- 가능한 경우 Span
나 Memory 를 사용해 안전성 확보 - 메모리 할당 시 fixed 문 사용 필수
- 포인터 연산 전 항상 경계 검사 수행
- 예외 발생 시 finally 블록을 통해 자원 해제