콘텐츠로 이동

C# unsafe 코드와 포인터 활용

C#은 기본적으로 메모리 안전한 관리 코드를 실행하지만, unsafe 키워드를 사용하면 C/C++ 수준의 포인터 연산이 가능합니다. 고성능 I/O, 인터롭, 이미지 처리 등 특수한 시나리오에서 필수적인 도구입니다.


.csproj
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
// 메서드 수준
public static unsafe void ProcessData(byte* ptr, int length) { }
// 클래스 수준
public unsafe class NativeBuffer { }
// 블록 수준
public void Example()
{
unsafe
{
int x = 42;
int* p = &x;
*p = 100;
Console.WriteLine(x); // 100
}
}

unsafe
{
int[] arr = { 10, 20, 30, 40, 50 };
fixed (int* p = arr) // GC가 배열을 이동하지 못하도록 고정
{
// 포인터 산술
for (int i = 0; i < arr.Length; i++)
{
Console.WriteLine(*(p + i)); // 10, 20, 30, 40, 50
}
// 인덱서 문법
Console.WriteLine(p[2]); // 30
// 포인터 증가
int* p2 = p;
p2++; // 4바이트 이동 (int 크기)
Console.WriteLine(*p2); // 20
}
}

GC(Garbage Collector)는 메모리 압축 시 객체를 이동시킵니다. 포인터를 사용하는 동안 GC가 객체를 이동하면 포인터가 무효화됩니다. fixed 문은 해당 객체를 일시적으로 고정합니다.

class ImageProcessor
{
private byte[] _buffer = new byte[1024 * 1024];
public unsafe void Fill(byte value)
{
fixed (byte* ptr = _buffer)
{
byte* p = ptr;
byte* end = ptr + _buffer.Length;
// 8바이트씩 처리 (unrolled loop)
while (p + 8 <= end)
{
*(ulong*)p = 0x0101010101010101UL * value;
p += 8;
}
// 나머지
while (p < end) *p++ = value;
}
// fixed 블록 종료 후 고정 해제
}
// string 고정
public unsafe void ProcessString(string s)
{
fixed (char* pStr = s)
{
char* p = pStr;
while (*p != '\0')
{
// 각 문자 처리
p++;
}
}
}
}

힙 대신 스택에 메모리를 할당합니다. 블록 종료 시 자동 해제되며 GC 압력이 없습니다.

// unsafe 없이도 Span<T>과 함께 사용 가능 (C# 7.3+)
public static int SumSmall(ReadOnlySpan<int> data)
{
Span<int> temp = stackalloc int[data.Length]; // 힙 할당 없음
data.CopyTo(temp);
// 정렬 후 합산
temp.Sort();
int sum = 0;
foreach (int v in temp) sum += v;
return sum;
}
// unsafe 버전
public static unsafe int FastSum(int* data, int n)
{
int* temp = stackalloc int[n]; // 스택에 n*4바이트 할당
for (int i = 0; i < n; i++) temp[i] = data[i];
// 블록 종료 시 자동 해제 (free 불필요)
int sum = 0;
for (int i = 0; i < n; i++) sum += temp[i];
return sum;
}

주의: 스택 크기는 보통 1MB~8MB입니다. stackalloc으로 큰 배열을 할당하면 스택 오버플로우가 발생합니다.


struct Vector3
{
public float X, Y, Z;
}
unsafe
{
Vector3 v = new Vector3 { X = 1.0f, Y = 2.0f, Z = 3.0f };
Vector3* p = &v;
p->X = 10.0f; // 포인터로 멤버 접근
Console.WriteLine(v.X); // 10
// 배열 of struct
Vector3[] vectors = new Vector3[100];
fixed (Vector3* pv = vectors)
{
for (int i = 0; i < 100; i++)
{
pv[i].X = i;
pv[i].Y = i * 2.0f;
pv[i].Z = i * 3.0f;
}
}
}

using System.Runtime.InteropServices;
public static unsafe void Example()
{
byte[] buffer = new byte[256];
fixed (byte* ptr = buffer)
{
// 포인터 → Span (unsafe 없이 사용 가능)
Span<byte> span = new Span<byte>(ptr, buffer.Length);
span.Fill(0xFF);
}
// MemoryMarshal을 통한 변환
Span<byte> byteSpan = buffer.AsSpan();
Span<int> intSpan = MemoryMarshal.Cast<byte, int>(byteSpan);
// 256 bytes → 64 ints
}

using System.Runtime.InteropServices;
class NativeInterop
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern unsafe bool WriteFile(
IntPtr hFile,
byte* lpBuffer,
int nNumberOfBytesToWrite,
out int lpNumberOfBytesWritten,
IntPtr lpOverlapped
);
public static unsafe void WriteToHandle(IntPtr handle, byte[] data)
{
fixed (byte* pData = data)
{
bool success = WriteFile(handle, pData, data.Length,
out int written, IntPtr.Zero);
if (!success)
throw new InvalidOperationException(
$"WriteFile failed: {Marshal.GetLastWin32Error()}");
}
}
}

8. 고정 크기 버퍼 (Fixed-size Buffer)

섹션 제목: “8. 고정 크기 버퍼 (Fixed-size Buffer)”
// 관리 구조체 안에 인라인 배열 (C 스타일 struct 모방)
unsafe struct PacketHeader
{
public fixed byte Magic[4]; // 4바이트 인라인 배열
public int Length;
public ushort Checksum;
public fixed byte Reserved[2];
}
unsafe
{
PacketHeader header;
header.Magic[0] = 0xDE;
header.Magic[1] = 0xAD;
header.Magic[2] = 0xBE;
header.Magic[3] = 0xEF;
header.Length = 1024;
}

상황권장
일반 고성능 코드Span<T>, Memory<T>
외부 네이티브 라이브러리 연동unsafe + P/Invoke
레거시 비관리 APIunsafe
스택 임시 버퍼stackalloc + Span<T>
구조체 내 인라인 배열fixed 버퍼

unsafe 코드는 강력하지만 GC 안전성, 타입 안전성, 메모리 안전성을 모두 프로그래머가 직접 보장해야 합니다. .NET 6+ 환경에서는 Span<T>, Memory<T>, MemoryMarshal API가 대부분의 고성능 시나리오를 unsafe 없이 처리할 수 있으므로, 반드시 필요한 경우(네이티브 인터롭, 특수 최적화)에만 unsafe를 사용하세요.