C# - 초기화되지 않은 메모리에 접근하는 버그?
닷넷 데브에 아래와 같은 질문이 있었군요. ^^
초기화되지 않은 메모리에 접근하는 버그?
; https://forum.dotnetdev.kr/t/topic/12763
실제로 질문에 있는
코드를 실행해 보면, 의도한 결과가 나오지 않습니다. 사실 이것은 소스 코드가 좀 복잡한 것일 뿐, 스택에 위치한 변수를 참조값으로 반환하기 때문에 발생한 문제로 간단하게 정리할 수 있습니다.
예를 들어, 여러분은 아래의 코드에서 컴파일 오류가 발생하는 것은 이해할 수 있을 것입니다.
class Program
{
static void Main(string[] args)
{
ref int data = ref GetInt();
Console.WriteLine(data);
}
static public ref int GetInt()
{
int n = 5;
return ref n; // 컴파일 오류: error CS8168: Cannot return local 'n' by reference because it is not a ref local
}
}
로컬 변수는 스택에 위치해 있고, 그 스택의 참조 주소를 반환해 사용하려고 하기 때문에 C# 컴파일러는 버그가 발생할 수 있는 이런 코드를 아예 컴파일 단계에서 오류를 발생시키는 것입니다.
(혹시 위의 오류가 이해가 안 된다면 "
C# - 관리 포인터로서의 ref 예약어 의미" 글을 참고해 보세요.)
자, 그럼 일단 저 코드가 왜 오류가 발생하는지 이해했다면, "
초기화되지 않은 메모리에 접근하는 버그?" 문제가 결국 저 위의 코드에서 (C# 컴파일러가 의도적으로 막았던) "ref n" 반환을 강제로 성공하도록 만들었기 때문에 발생한 문제임을 알 수 있습니다.
비유하자면, 아래와 같이 컴파일이 가능하게 만들어 버린 것입니다.
static public ref int GetInt()
{
int n = 5;
ref var unsafeValueRef = ref Unsafe.AsRef(ref n);
return ref unsafeValueRef; // 결국 "return ref n;"을 성공시킨 것
}
즉, 스택에 놓인 변수 n의 참조를 반환한 것이므로 GetInt 메서드 호출 후 스택이 깨지지 않은 상태라면 의도한 값이 나올 수 있습니다.
ref int data = ref GetInt();
Console.WriteLine(data); // 출력: 5
하지만, 곧바로 값을 사용하기 전에 스택을 어지럽힐 수 있는 코드가 중간에 하나 끼인다면?
ref int data = ref GetInt();
Console.WriteLine(); // 이 호출로 인해 스택에 보관된 n = 5 변수의 값이 변경될 수 있음
Console.WriteLine(data); // 출력: (정해지지 않은 값)
저렇게 개발자가 의도하지 않은 값을 얻게 되는 것입니다. (그러니까, "Unsafe"는 이름 그대로 사용에 주의해야 합니다.)
"
초기화되지 않은 메모리에 접근하는 버그?" 문제는, 위의 결과가 발생할 수 있는 상황을 좀 더 복잡하게 코드를 꼬아 어려워 보이는 것일 뿐 결국 같은 유형의 문제에 해당합니다.
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
UnsafeNumber? n1 = new UnsafeNumber(123);
Console.WriteLine(n1?.number);
ref readonly T AsRef<T>() where T : struct => ref MemoryMarshal.AsRef<T>(n1.Value.BytesPack);
ref readonly var n2 = ref AsRef<UnsafeNumber>();
// Console.WriteLine(); // 이 코드의 실행 유무에 따라 결과가 달라집니다.
Console.WriteLine(n2.number);
record struct UnsafeNumber(UInt16 number)
{
public ReadOnlySpan<byte> BytesPack => MemoryMarshal.AsBytes(new ReadOnlySpan<UnsafeNumber>(in Unsafe.AsRef(ref this)));
}
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]