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

C# 9.0 - (2) localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)

C# 9.0 - (1) 대상으로 형식화된 new 식(Target-typed new expressions)
; https://www.sysnet.pe.kr/2/0/12363

C# 9.0 - (2) localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)
; https://www.sysnet.pe.kr/2/0/12364

C# 9.0 - (3) 람다 메서드의 매개 변수 무시(Lambda discard parameters)
; https://www.sysnet.pe.kr/2/0/12365

C# 9.0 - (4) 원시 크기 정수(Native ints)
; https://www.sysnet.pe.kr/2/0/12366

C# 9.0 - (5) 로컬 함수에 특성 지정 가능(Attributes on local functions)
; https://www.sysnet.pe.kr/2/0/12372

C# 9.0 - (6) 함수 포인터(Function pointers)
; https://www.sysnet.pe.kr/2/0/12374

C# 9.0 - (7) 패턴 일치 개선 사항(Pattern matching enhancements)
; https://www.sysnet.pe.kr/2/0/12383

C# 9.0 - (8) 정적 익명 함수 (static anonymous functions)
; https://www.sysnet.pe.kr/2/0/12389

C# 9.0 - (9) 레코드 (Records)
; https://www.sysnet.pe.kr/2/0/12392

C# 9.0 - (10) 대상으로 형식화된 조건식(Target-typed conditional expressions)
; https://www.sysnet.pe.kr/2/0/12399

C# 9.0 - (11) 공변 반환 형식(Covariant return types)
; https://www.sysnet.pe.kr/2/0/12402

C# 9.0 - (12) foreach 루프에 대한 GetEnumerator 확장 메서드 지원(Extension GetEnumerator)
; https://www.sysnet.pe.kr/2/0/12403

C# 9.0 - (13) 모듈 이니셜라이저(Module initializers)
; https://www.sysnet.pe.kr/2/0/12404

C# 9.0 - (14) 부분 메서드에 대한 새로운 기능(New features for partial methods)
; https://www.sysnet.pe.kr/2/0/12405

C# 9.0 - (15) 최상위 문(Top-level statements)
; https://www.sysnet.pe.kr/2/0/12406

C# 9.0 - (16) 제약 조건이 없는 형식 매개변수 주석(Unconstrained type parameter annotations)
; https://www.sysnet.pe.kr/2/0/12423




C# 언어는, 기본적으로 모든 로컬 변수의 값을 (기존 스택의 쓰레기 값을 가지지 않도록) 0으로 초기화합니다. 그리고 이에 대한 지원을 IL 언어 수준에서부터 ".locals init" 구문으로 지원을 하는데, 가령 다음과 같이 코딩을 한 경우,

using System;

class Program
{
    static void Main(string[] args)
    {
        LocalsInitMethod();
    }

    private static unsafe void LocalsInitMethod()
    {
        int i;
        int j;
    }
}

/* 출력 결과
0
0
*/

이것을 IL 언어로 번역해 보면 다음과 같이 ".locals init"에 로컬 변수들이 놓여 있는 것을 볼 수 있습니다.

.method private hidebysig static void LocalsInitMethod() cil managed
{
    .maxstack  0
    .locals init ([0] int32 i,
                [1] int32 j)
    IL_0000:  nop
    IL_0001:  ret
} // end of method Program::LocalsInitMethod

즉, ".locals init" 코드 덕분에 해당 변수는 모두 0으로 초기화되었던 것입니다.




그런데, 사실 C# 컴파일러 입장에서는 이미 로컬 변수의 값을 초기화하지 않고 사용하는 구문을 허용하지 않고 있습니다.

private static void LocalsInitMethod2()
{
    int i;
    int j;

    // 컴파일 오류    
    Console.WriteLine(i); // CS0165 Use of unassigned local variable 'i'
    Console.WriteLine(j); // CS0165 Use of unassigned local variable 'j'
}

따라서 개발자는 명시적으로 어떤 식으로든 할당을 해서 사용할 수밖에 없으므로,

private static void LocalsInitMethod2()
{
    int i = 5;
    int j = 6;

    Console.WriteLine(i);
    Console.WriteLine(j);
}

경우에 따라, i, j 변수의 값을 0으로 초기화를 한 다음 또다시 개발자의 코드에 의해 값을 할당하는 작업을 하는 것은 일종의 오버헤드로 작용할 수 있습니다.

이런 문제를 해결하는 것이 바로 SkipLocalsInit 특성을 적용하는 것입니다.

[SkipLocalsInit]
private static void NonLocalsInitMethod()
{
    int i;
    int j;
}

SkipLocalsInitAttribute 특성은 .NET 5의 BCL에만 정의되어 있으므로 다른 환경에서는 그냥 다음과 같이 임의로 특성을 정의해 넣으면 됩니다.

#if !NET5_0
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)]
    public sealed class SkipLocalsInitAttribute : Attribute
    {
    }
}
#endif

이와 함께 C# 프로젝트 속성 창에서 "Build" 패널의 "Allow unsafe code" 옵션을 켜야 합니다. (해당 옵션을 켜지 않으면 "error CS0227: Unsafe code may only appear if compiling with /unsafe" 오류가 발생합니다.)

그렇게 해서 빌드를 하면 이제 ".locals init"에서 ".locals"로 바뀌어 IL 코드가 생성됩니다.

.method private hidebysig static void NonLocalsInitMethod() cil managed
{
    .custom instance void System.Runtime.CompilerServices.SkipLocalsInitAttribute::.ctor() = ( 01 00 00 00 ) 
    // Code size       2 (0x2)
    .maxstack  0
    .locals ([0] int32 i,
                [1] int32 j)
    IL_0000:  nop
    IL_0001:  ret
} // end of method Program::NonLocalsInitMethod

따라서, 더 이상 명시적인 0 초기화는 하지 않고, 스레드 스택에 남아 있는 값을 그대로 사용하게 되어, 아래의 출력값이 매번 달라지는 것을 확인할 수 있습니다.

[SkipLocalsInitAttribute]
private static unsafe void NonLocalsInitMethod()
{
    int i;
    int j;

    int* pi = &i;
    int* pj = &j;

    Console.WriteLine(*pi); // 출력 결과: 예측할 수 없음
    Console.WriteLine(*pj); // 출력 결과: 예측할 수 없음
}

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




사실 저도 좀 궁금하긴 했는데, 굳이 "unsafe" 옵션이 필요한가?에 대한 의견이 다음의 덧글에 나옵니다.

Suppress emitting of localsinit flag. (VS 16.8, .NET 5) 
; https://github.com/dotnet/csharplang/issues/1738

일반적으로 C#에서 배열 역시 0 초기화가 되는데,

int [] arr = new int [100];

위의 경우, 당연히 모든 요소가 0 초기화가 되어 있으므로 C# 컴파일러 입장에서는 safe한 코드임을 보장할 수 있습니다. 하지만, 위의 코드가 Span<T>와 함께 stackalloc으로 바뀌게 되면,

/*
Span<T>를 .NET Framework에서 사용하려는 경우, 
System.Memory를 NuGet에서 참조 추가해야 하며 "target framework"를 최소 "4.5" 이상의 버전으로 올려야 합니다.
*/
private static void LocalsInitStackAlloc()
{
    Span<int> arr = stackalloc int[100]; // 현재는 int 배열의 요소가 0임을 보장할 수 있지만,
}

SkipLocalsInitAttribute 특성을 적용하는 경우,

[SkipLocalsInitAttribute]
private static void LocalsInitStackAlloc()
{
    Span<int> arr = stackalloc int[100]; // int 배열의 요소가 0임을 보장할 수 없음.
}

더 이상 코드가 안전하다고 보장할 수 없으므로 결국 C# 컴파일러의 /unsafe 옵션을 켜도록 강제하게 된 것입니다. (그래도 그렇지... 그럼 그냥 stackalloc을 포함한 경우에만 /unsafe 옵션을 강제했으면 어땠을까... 싶군요. ^^;)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/6/2024]

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

비밀번호

댓글 작성자
 




1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13474정성태12/6/20232133개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/20232325닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/20232154C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/20232181Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
13470정성태12/2/20232482닷넷: 2178. C# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입파일 다운로드1
13469정성태12/1/20232188닷넷: 2177. C# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법파일 다운로드1
13468정성태12/1/20232175닷넷: 2176. C# - .NET Core/5+부터 달라진 RCW(Runtime Callable Wrapper) 대응 방식파일 다운로드1
13467정성태11/30/20232169오류 유형: 882. C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5)파일 다운로드1
13466정성태11/29/20232376닷넷: 2175. C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션
13465정성태11/28/20232108개발 환경 구성: 689. MSBuild - CopyToOutputDirectory가 "dotnet publish" 시에는 적용되지 않는 문제파일 다운로드1
13464정성태11/28/20232226닷넷: 2174. C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합파일 다운로드1
13463정성태11/27/20232143오류 유형: 881. Visual Studio - NU1605: Warning As Error: Detected package downgrade
13462정성태11/27/20232207오류 유형: 880. Visual Studio - error CS0246: The type or namespace name '...' could not be found
13461정성태11/26/20232258닷넷: 2173. .NET Core 3/5+ 기반의 COM Server를 registry 등록 없이 사용하는 방법파일 다운로드1
13460정성태11/26/20232210닷넷: 2172. .NET 6+ 기반의 COM Server 내에 Type Library를 내장하는 방법파일 다운로드1
13459정성태11/26/20232193닷넷: 2171. .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법파일 다운로드1
13458정성태11/26/20232198닷넷: 2170. .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp)
13457정성태11/25/20232150VS.NET IDE: 187. Visual Studio - 16.9 버전부터 추가된 "Display inline type hints" 옵션
13456정성태11/25/20232447닷넷: 2169. C# - OpenAI를 사용해 PDF 데이터를 대상으로 OpenAI 챗봇 작성 [1]파일 다운로드1
13455정성태11/25/20232339닷넷: 2168. C# - Azure.AI.OpenAI 패키지로 OpenAI 사용파일 다운로드1
13454정성태11/23/20232681닷넷: 2167. C# - Qdrant Vector DB를 이용한 Embedding 벡터 값 보관/조회 (Azure OpenAI) [1]파일 다운로드1
13453정성태11/23/20232217오류 유형: 879. docker desktop 설치 시 "Invalid JSON string. (Exception from HRESULT: 0x83750007)"
13452정성태11/22/20232296닷넷: 2166. C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법파일 다운로드1
13451정성태11/21/20232431닷넷: 2165. C# - Azure OpenAI API를 이용해 ChatGPT처럼 동작하는 콘솔 응용 프로그램 제작파일 다운로드1
13450정성태11/21/20232253닷넷: 2164. C# - Octokit을 이용한 GitHub Issue 검색파일 다운로드1
13449정성태11/21/20232351개발 환경 구성: 688. Azure OpenAI 서비스 신청 방법
1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...