Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

C# - safe/unsafe 문맥에 대한 C# 13의 (하위 호환을 깨는) 변화

이 글의 실습은 현재(2024-08-05) Visual Studio 2022 Preview 버전(최소 17.11.p2)에서만 가능합니다.




Unsafe 문맥은,

23 Unsafe code
; https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/unsafe-code.md#232-unsafe-contexts

Other than establishing an unsafe context, thus permitting the use of pointer types, the unsafe modifier has no effect on a type or a member.


문서에도 나오듯이, 포인터를 사용할 수 있도록 허용하는 것 외에는 별달리 특별한 기능은 없습니다. 그리고 그에 대한 문맥 설정은 unsafe 예약어를 통해 블록으로 설정할 수 있고,

void Method() // 메서드 전체 구간에서는 safe 문맥
{
    unsafe // 메서드 내에서 아래의 블록 내에서만 unsafe 문맥으로 바뀜
    {
        int* p = stackalloc int[10];
    }
}

상위 구간에서 설정되면 하위 영역은 자연스럽게 문맥을 이어받게 됩니다.

unsafe void Method() // 메서드 전체 구간에 unsafe 문맥
{
    int* p = stackalloc int[10]; // 따라서 별도의 unsafe 블록 없이도 포인터 사용 가능

    void LocalFunc() // 메서드가 포함하는 로컬 함수는 그 메서드를 포함한 문맥을 상속
    {
        int* p2 = stackalloc int[10]; // 따라서 별도의 unsafe 블록 없이도 포인터 사용 가능
    }
}

public unsafe class MyClass // 타입의 전체 구간에 unsafe 문맥
{
    void Method() 
    {
        int* p = stackalloc int[10]; // 따라서 별도의 unsafe 블록 없이도 포인터 사용 가능
    }
}

재미있는 건 unsafe 예약어는 있어도 safe 예약어는 없다는 점입니다. 이로 인해 unsafe 문맥 내에서 특정 영역을 다시 safe 문맥으로 돌리는 방법은 없습니다.

그런데, 이에 대한 예외가 하나(?) 있는데요,

13.3.1 General
; https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/statements.md#1331-general

It is a compile-time error for an iterator block to contain an unsafe context (§23.2). An iterator block always defines a safe context, even when its declaration is nested in an unsafe context.


iterator 메서드의 경우 내부 block을 무조건 safe 문맥으로 설정한다는 점입니다. 그래서 아래와 같은 코드의 경우,

using System.Collections.Generic;

unsafe public class Program
{
    static void Main(string[] args)
    {
        int* p = stackalloc int[2]; // 일반 메서드는 타입에 지정된 unsafe 문맥이 적용되지만,
    }

    public static IEnumerator<int> GetNumbers()
    { // iterator 메서드의 내부 블록은 무조건 safe 문맥 적용

        int* p = stackalloc int[2]; // 컴파일 오류 - error CS1629: 반복기에는 안전하지 않은 코드를 사용할 수 없습니다.
                                    // Unsafe code may not appear in iterators

        yield return 1;
    }
}

(타입에 지정한 unsafe의 영향으로) Main 메서드 내에서는 unsafe 코드가 허용되지만 GetNumbers iterator 메서드 블록 내에서는 CS1629 컴파일 오류가 발생합니다. 게다가 (C# 12 이하에서는) 아예 unsafe 블록 자체를 내부에 허용하지 않기 때문에 결과적으로 포인터를 사용한 코드를 작성할 수 없습니다.




iterator 메서드에 특이한 점이 하나 더 있다면, safe 문맥이라고 강제로 적용됨에도 불구하고 그 메서드가 포함한 로컬 함수는 iterator 메서드가 아닌 상위 범위의 문맥을 상속한다는 점입니다. 그래서 다음과 같은 코드는 허용이 됩니다.

unsafe public class Program // unsafe 문맥
{
    static void Main(string[] args)
    {
        int* p = stackalloc int[2];
    }

    public static IEnumerator<int> GetNumbers()
    { // iterator 메서드의 내부 블록은 무조건 safe 문맥 적용

        LocalFunc();

        yield return 1;

        void LocalFunc() // 하지만, 로컬 함수는 타입에 지정했던 unsafe 문맥을 상속
        {
            int* p = stackalloc int[2]; // 포인터 코드 사용 가능
        }
    }
}

약간 혼란스럽죠? ^^ 문서에 의하면 위의 경우 (C# 12 이하의) Roslyn은 iterator 메서드를 실제론 unsafe 문맥으로 처리하면서도, 단지 내부에서만 unsafe 구문을 허용하지 않는 식으로 처리한다고 합니다.

C# 13 컴파일러에서 변화된 것이 바로 저 규칙입니다. 블록 내부 전체에서 unsafe를 사용하지 못하도록 강제하는 것이 아닌, 일부 영역을 unsafe로 지정 가능하도록 바뀌었고 내부에서 정의하는 로컬 함수의 문맥도 iterator 블록의 safe 문맥을 따라가도록 했습니다. 그래서 저 코드를 그대로 C# 13에서 빌드하면,

unsafe public class Program
{
    public static IEnumerator<int> GetNumbers()
    { // 여기서 다시 safe 문맥으로 바뀌고,
        LocalFunc();

        yield return 1;

        void LocalFunc() // 로컬 함수도 safe 문맥이므로,
        {
            int* p = stackalloc int[2]; // 컴파일 에러 - error CS0214: Pointers and fixed size buffers may only be used in an unsafe context
        }
    }
}

CS0214 컴파일 에러가 발생합니다. 즉, iterator 메서드에 강제로 적용된 safe 문맥을 로컬 함수(위의 경우 LocalFunc)에 내려주므로 포인터 코드를 사용할 수 없게 되었습니다.

이렇게 하위 호환이 깨지긴 했지만 해당 영역을 unsafe 문맥으로 지정하는 것으로,

/* 또는, unsafe */ void LocalFunc()
{
    unsafe
    {
        int* p = stackalloc int[2];
    }
}

쉽게 해결할 수는 있습니다.




이번 글은 사실 C# 13의 "Allow ref and unsafe in iterators and async"에 명시하고 있는 "Breaking changes"의 내용에 포함된 내용을 정리한 것입니다.

Allow ref and unsafe in iterators and async
- Breaking changes
; https://github.com/dotnet/csharplang/blob/main/proposals/ref-unsafe-in-iterators-async.md#breaking-changes

그리고 보다 더 복잡한 규칙은 다음과 같은데요, 이번 글을 이해하셨다면 눈에 들어올 것입니다. ^^

// https://github.com/dotnet/csharplang/blob/main/proposals/ref-unsafe-in-iterators-async.md#detailed-design

using System.Collections.Generic;
using System.Threading.Tasks;

class A : System.Attribute { }
unsafe partial class C1
{ // unsafe context
    [/* unsafe context */ A]
    IEnumerable<int> M1(
        /* unsafe context */ int*[] x)
    { // safe context (this is the iterator block implementing the iterator)
        yield return 1;
    }
    IEnumerable<int> M2()
    { // safe context (this is the iterator block implementing the iterator)
        unsafe
        { // unsafe context
            { // unsafe context (this is *not* the block implementing the iterator)
                yield return 1; // error: `yield return` in unsafe context
            }
        }
    }
    [/* unsafe context */ A]
    unsafe IEnumerable<int> M3(
        /* unsafe context */ int*[] x)
    { // safe context
        yield return 1;
    }
    [/* unsafe context */ A]
    IEnumerable<int> this[
        /* unsafe context */ int*[] x]
    { // unsafe context
        get
        { // safe context
            yield return 1;
        }
        set { /* unsafe context */ }
    }
    [/* unsafe context */ A]
    unsafe IEnumerable<int> this[
        /* unsafe context */ long*[] x]
    { // unsafe context (the iterator declaration is unsafe)
        get
        { // safe context
            yield return 1;
        }
        set { /* unsafe context */ }
    }
    IEnumerable<int> M4()
    {
        yield return 1;
        var lam1 = async () =>
        { // safe context
          // spec violation: in Roslyn, this is an unsafe context in LangVersion 12 and lower
            await Task.Yield(); // error in C# 12, allowed in C# 13
            int* p = null; // error in both C# 12 and C# 13 (unsafe in iterator)
        };
        unsafe
        {
            var lam2 = () =>
            { // unsafe context, lambda cannot be an iterator
                yield return 1; // error: yield cannot be used in lambda
            };
        }
        async void local()
        { // safe context
          // spec violation: in Roslyn, this is an unsafe context in LangVersion 12 and lower
            await Task.Yield(); // error in C# 12, allowed in C# 13
            int* p = null; // allowed in C# 12, error in C# 13 (breaking change in Roslyn)
        }
        local();
    }
    public partial IEnumerable<int> M5() // unsafe context (inherits from parent)
    { // safe context
        yield return 1;
    }
}
partial class C1
{
    public partial IEnumerable<int> M5(); // safe context (inherits from parent)
}
class C2
{ // safe context
    [/* unsafe context */ A]
    unsafe IEnumerable<int> M(
        /* unsafe context */ int*[] x)
    { // safe context
        yield return 1;
    }
    unsafe IEnumerable<int> this[
        /* unsafe context */ int*[] x]
    { // unsafe context
        get
        { // safe context
            yield return 1;
        }
        set { /* unsafe context */ }
    }
}




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/5/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1780정성태10/15/201424256오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201419771오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201418234오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421341오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418349오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419466오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426661개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429831.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418619.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201428123개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421719개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424144개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419561개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424687VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422528개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201426071.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422316오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423883.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424570.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421643.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419915.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201431027개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437919개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418483오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427486기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434279오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...