fixed 문 — 가변 변수를 고정해 포인터 연산에 사용하기
fixed 문 — 가변 변수를 고정해 포인터 연산에 사용하기
C#에서 포인터를 다룰 때, 가비지 컬렉터가 메모리를 옮겨버리면 참조하던 주소가 의미를 잃어버리는 문제가 있어요. fixed 문은 그런 가변(moveable) 변수를 한 곳에 고정(pin)하고, 그 변수를 가리키는 포인터를 선언할 수 있게 해주는 문장입니다.
출처: Microsoft Learn
본문
fixed 문은 가비지 컬렉터가 가변 변수를 다른 곳으로 옮기지 못하게 막고, 그 변수를 가리키는 포인터를 선언해 줍니다. 고정된(pinned) 변수의 주소는 이 문장이 실행되는 동안 변하지 않아요. 그리고 이렇게 선언한 포인터는 해당 fixed 문 안에서만 쓸 수 있고, 읽기 전용이라 값을 바꿀 수도 없습니다:
unsafe
{
byte[] bytes = [1, 2, 3];
fixed (byte* pointerToFirst = bytes)
{
Console.WriteLine($"The address of the first array element: {(long)pointerToFirst:X}.");
Console.WriteLine($"The value of the first array element: {*pointerToFirst}.");
}
}
// Output is similar to:
// The address of the first array element: 2173F80B5C8.
// The value of the first array element: 1.
[!NOTE]
fixed문을 쓰려면 코드를 AllowUnsafeBlocks 컴파일러 옵션을 켜고 컴파일해야 해요.C# 15의 메모리 안전성(memory safety) 미리 보기 기능을 쓰면
unsafe컨텍스트 밖에서도fixed를 쓸 수 있지만, 포인터 역참조처럼 고정된 메모리에 실제로 접근하는 연산은 여전히unsafe컨텍스트가 필요합니다.
선언한 포인터는 여러 가지 방법으로 초기화할 수 있어요.
- 배열로 초기화 — 이 문서 맨 앞의 예시처럼 하면 됩니다. 이때 초기화된 포인터는 배열의 첫 번째 요소의 주소를 가리켜요.
- 변수의 주소로 초기화 — 주소 연산자
&를 사용해요. 아래 예시처럼요:
unsafe
{
int[] numbers = [10, 20, 30];
fixed (int* toFirst = &numbers[0], toLast = &numbers[^1])
{
Console.WriteLine(toLast - toFirst); // output: 2
}
}
객체의 필드도 고정할 수 있는 또 다른 가변 변수의 예시예요. 초기화된 포인터가 어떤 객체의 필드나 배열 요소의 주소를 가리키는 경우, fixed 문은 문장 본문이 실행되는 동안 가비지 컬렉터가 해당 객체 인스턴스를 옮기거나 해제하지 않는다는 것을 보장해 줍니다.
GetPinnableReference메서드를 구현한 타입의 인스턴스로 초기화 — 그 메서드는 반드시 비관리형(unmanaged) 타입에 대한ref변수를 반환해야 해요. .NET의Span<T>와ReadOnlySpan<T>타입이 이 패턴을 사용하죠. 그래서 Span 인스턴스도 다음과 같이 고정할 수 있습니다:
unsafe
{
int[] numbers = [10, 20, 30, 40, 50];
Span<int> interior = numbers.AsSpan()[1..^1];
fixed (int* p = interior)
{
for (int i = 0; i < interior.Length; i++)
{
Console.Write(p[i]);
}
// output: 203040
}
}
자세한 내용은 Span<T>.GetPinnableReference API 참조를 확인해 보세요.
- 문자열(string)로 초기화 — 아래 예시처럼요:
unsafe
{
var message = "Hello!";
fixed (char* p = message)
{
Console.WriteLine(*p); // output: H
}
}
- 고정 크기 버퍼(fixed-size buffer)로 초기화 — 고정 크기 버퍼를 선언하는 방법도 있어요.
한편 스택에 메모리를 할당하면 가비지 컬렉션의 대상이 아니어서 굳이 고정할 필요가 없어요. 그럴 때는 stackalloc 식을 쓰면 됩니다.