다음을 통해 공유


localsinit 플래그 내보내기를 억제합니다.

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는관련 LDM(언어 디자인 모임) 노트에서 캡처됩니다.

사양문서에서 스펙렛을 C# 언어 표준으로 채택하는 프로세스에 대해 자세히 알아볼 수 있습니다.

요약

SkipLocalsInitAttribute 속성을 통해 localsinit 플래그의 방출을 억제할 수 있습니다.

동기

배경

참조를 포함하지 않는 CLR 사양별 지역 변수는 VM/JIT에 의해 특정 값으로 초기화되지 않습니다. 초기화 없이 이러한 변수를 읽는 것은 형식에 안전하지만, 그렇지 않으면 동작이 정의되지 않고 구현에 따라 다릅니다. 일반적으로 초기화되지 않은 로컬 변수에는 현재 스택 프레임이 차지하고 있는 메모리에 남겨진 값이 포함되어 있을 수 있습니다. 이로 인해 비결정적 동작이 발생할 수 있으며 버그를 재현하기 어려울 수 있습니다.

지역 변수를 "할당"하는 방법에는 두 가지가 있습니다.

  • 값을 저장하거나
  • 로컬 메모리 풀에서 할당된 모든 항목이 0으로 초기화되도록 강제하는 localsinit 플래그를 지정합니다. 여기에는 지역 변수와 stackalloc 데이터가 모두 포함됩니다.

초기화되지 않은 데이터의 사용은 권장되지 않으며 확인 가능한 코드에서는 허용되지 않습니다. 흐름 분석을 통해 증명할 가능성은 있지만, localsinit이 설정되도록 단순히 요구하는 보수적인 검증 알고리즘도 허용된다는 점이 인정됩니다.

지금까지 C# 컴파일러는 로컬을 선언하는 모든 메서드에서 localsinit 플래그를 내보낸다.

C#은 CLR 사양에 필요한 것보다 더 엄격한 명확한 할당 분석을 사용하지만(C#은 지역 주민의 범위 지정도 고려해야 합니다) 결과 코드를 공식적으로 확인할 수 있다고 엄격하게 보장되지는 않습니다.

  • CLR 및 C# 규칙은 로컬 변수를 out 인수로 전달하는 것이 use에 해당하는지 여부에 대해 일치하지 않을 수 있습니다.
  • 조건이 알려진 경우 CLR 및 C# 규칙은 조건부 분기 처리에 동의하지 않을 수 있습니다(일정한 전파).
  • CLR은 그것이 허용되므로 단순히 localinits를 요구할 수도 있습니다.

문제

고성능 애플리케이션에서는 강제 제로 초기화 비용이 눈에 띄게 될 수 있습니다. stackalloc 사용될 때 특히 두드러집니다.

경우에 따라 JIT는 후속 할당에 의해 이러한 초기화가 "종료"될 때 개별 로컬의 초기 0 초기화를 방지할 수 있습니다. 모든 JIT가 이 작업을 수행하는 것은 아니며 이러한 최적화에는 제한이 있습니다. stackalloc에는 도움이 되지 않습니다.

문제가 실제로 존재한다는 것을 설명하기 위해, IL 로컬 변수를 포함하지 않는 메서드에 localsinit 플래그가 없는 현상이 발생하는 알려진 버그가 있습니다. 초기화 비용을 방지하기 위해 의도적으로 이러한 메서드에 stackalloc 배치하여 사용자가 버그를 이미 악용하고 있습니다. 이는 IL 지역 주민이 없다는 사실에도 불구하고 불안정한 메트릭이며 codegen 전략의 변경에 따라 달라질 수 있습니다. 버그를 수정해야 하며 사용자는 플래그를 숨기는 보다 문서화되고 신뢰할 수 있는 방법을 찾아야 합니다.

상세 디자인

localsinit 플래그를 내보내지 않도록 컴파일러에 지시하는 방법으로 System.Runtime.CompilerServices.SkipLocalsInitAttribute 지정할 수 있습니다.

결과적으로 JIT에 의해 로컬 변수가 0으로 초기화되지 않을 수 있으며, 이는 대부분의 경우 C#에서 관찰할 수 없는 현상입니다.
이 외에도 stackalloc 데이터는 0으로 초기화되지 않습니다. 그것은 확실히 관찰 할 수 있지만, 또한 가장 동기 부여 시나리오입니다.

허용되고 인식되는 특성 대상은 Method, Property, Module, Class, Struct, Interface, Constructor. 그러나 컴파일러는 나열된 대상을 사용하여 특성을 정의할 필요가 없으며 특성이 정의된 어셈블리를 관리하지 않습니다.

중첩된 메서드에 대한 메서드를 포함하는 컨테이너(class, module)에 특성을 지정하면 플래그는 컨테이너 내에 포함된 모든 메서드에 영향을 줍니다.

합성된 메서드는 논리 컨테이너/소유자로부터 플래그를 "상속"합니다.

플래그는 실제 메서드 본문에 대한 codegen 전략에만 영향을 줍니다. 즉, 플래그는 추상 메서드에 영향을 주지 않으며 메서드를 재정의/구현하는 데 전파되지 않습니다.

이는 명시적으로 컴파일러 기능이며 그리고가 아닌 언어 기능입니다.
컴파일러 명령줄 스위치와 마찬가지로, 이 기능은 특정 코드 생성 전략의 구현 세부 사항을 제어하며 C# 사양의 필수 사항이 아닙니다.

단점

  • 이전/다른 컴파일러는 특성을 적용하지 않을 수 있습니다. 특성을 무시하는 것은 호환되는 동작입니다. 성능에 약간의 영향이 있을 수 있습니다.

  • localinits 플래그가 없는 코드는 확인 실패를 트리거할 수 있습니다. 이 기능을 요청하는 사용자는 일반적으로 확인 가능성에 대해 걱정하지 않습니다.

  • 개별 메서드보다 높은 수준에서 특성을 적용하면 비로컬 효과가 있으며 stackalloc 사용될 때 관찰할 수 있습니다. 그러나 이것이 가장 요청된 시나리오입니다.

대안

  • unsafe 컨텍스트에서 메서드가 선언될 때 localinits 플래그를 생략합니다. 이로 인해 stackalloc경우 조용하고 위험한 동작이 결정적에서 비결정적 동작으로 변경될 수 있습니다.

  • 항상 localinits 플래그를 생략합니다. 위보다 더 나쁘다.

  • 메서드 본문에 stackalloc 사용되지 않는 한 localinits 플래그를 생략합니다. 가장 요청된 시나리오를 다루지 않으며, 이를 되돌릴 수 있는 옵션이 없어 코드가 검증 불가능할 수 있습니다.

해결되지 않은 질문

  • 특성을 실제로 메타데이터로 내보내야 하나요?

디자인 회의

아직 없음.