아래 샘플 코드는 자연스럽게 발생했습니다. 갑자기 내 코드는 매우 불쾌한 FatalExecutionEngineError
예외입니다. 범인 샘플을 격리하고 최소화하려고 30 분을 보냈습니다. Visual Studio 2012를 콘솔 앱으로 사용하여이를 컴파일하십시오.
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
.NET Framework 4 및 4.5에서이 오류가 발생해야합니다.
이것은 알려진 버그입니까? 원인은 무엇이며 완화하기 위해 무엇을 할 수 있습니까? 내 현재 해결 방법은 사용하지 않는 string.Empty
것이지만 잘못된 트리를 짖고 있습니까? 해당 코드에 대한 내용을 변경하면 예상대로 작동합니다 (예 : 빈 정적 생성자 제거 A
또는 형식 매개 변수를에서 object
로 변경) int
.
랩톱 에서이 코드를 사용해 보았지만 불평하지 않았습니다. 그러나 기본 앱을 사용해 보았고 랩톱에서도 충돌했습니다. 문제를 줄일 때 무언가를 엉망으로 만들었어야했는데, 그것이 무엇인지 알아낼 수 있는지 살펴 보겠습니다.
내 노트북은 프레임 워크 4.0에서 위와 동일한 코드로 충돌했지만 4.5에서도 주요 충돌이 발생했습니다. 두 시스템 모두 최신 업데이트와 함께 VS’12를 사용하고 있습니다 (7 월?).
더 많은 정보 :
- IL 코드 (컴파일 된 디버그 / 모든 CPU / 4.0 / VS2010 (IDE가 중요하지 않습니까?)) : http://codepad.org/boZDd98E
- VS 2010에는 4.0이 표시되지 않습니다. 최적화, 다른 대상 CPU, 디버거 연결 / 연결되지 않음 등으로 충돌하지 않거나 충돌하지 않음 -Tim Medora
- AnyCPU를 사용하면 2010 년에 충돌이 발생합니다. x86에서는 정상입니다. 플랫폼 대상 = AnyCPU를 사용하지만 Visual Studio 2010 SP1에서 충돌하지만 플랫폼 대상 = x86에서는 문제가 있습니다. 이 시스템에는 VS2012RC도 설치되어 있으므로 4.5 교체가 가능합니다. 사용 anycpu를하고에 targetPlatform 그런 다음이 Framework.-의 회귀 같은 외모 때문에 충돌하지 않는 3.5 = colinsmith
- 4.0이 설치된 VS2010의 x86, x64 또는 AnyCPU에서 재생할 수 없습니다. – 후지
- x64, (2012rc, Fx4.5)에만 해당-Henk Holterman
- Win8 RP의 VS2012 RC. .NET 4.5를 타겟팅 할 때 처음에는이 MDA가 표시되지 않습니다. .NET 4.0을 대상으로 전환했을 때 MDA가 나타났습니다. 그런 다음 .NET 4.5로 다시 전환 한 후 MDA가 유지됩니다. – 웨인
답변
이것은 전체 답변이 아니지만 몇 가지 아이디어가 있습니다.
.NET JIT 팀의 답변이없는 사람없이 찾을 수있을만큼 좋은 설명을 찾았습니다.
최신 정보
좀 더 깊게 보았고 문제의 원인을 찾았습니다. JIT 유형 초기화 논리의 버그와 JIT가 의도 한대로 작동한다는 가정에 의존하는 C # 컴파일러의 변경으로 인해 발생하는 것으로 보입니다. JIT 버그는 .NET 4.0에 존재했지만 .NET 4.5의 컴파일러 변경으로 인해 발견되지 않았다고 생각합니다.
나는 이것이 beforefieldinit
유일한 문제 라고 생각하지 않습니다 . 나는 그것보다 간단하다고 생각합니다.
System.String
.NET 4.0의 mscorlib.dll 형식 에는 정적 생성자가 포함되어 있습니다.
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr ""
IL_0005: stsfld string System.String::Empty
IL_000a: ret
} // end of method String::.cctor
mscorlib.dll의 .NET 4.5 버전에서 String.cctor
(정적 생성자)는 눈에 띄지 않습니다.
….. 정적 생성자 없음 🙁 …..
두 버전 모두 String
유형이 다음과 beforefieldinit
같이 장식됩니다 .
.class public auto ansi serializable sealed beforefieldinit System.String
IL과 비슷하게 컴파일 할 유형을 만들려고했지만 정적 필드는 있지만 정적 생성자 .cctor
는 없습니다.하지만 할 수 없었습니다. 이러한 모든 유형은 .cctor
IL 에서 메소드를 갖습니다 .
public class MyString1 {
public static MyString1 Empty = new MyString1();
}
public class MyString2 {
public static MyString2 Empty = new MyString2();
static MyString2() {}
}
public class MyString3 {
public static MyString3 Empty;
static MyString3() { Empty = new MyString3(); }
}
내 생각 엔 .NET 4.0과 4.5 사이에서 두 가지가 바뀌 었다는 것입니다.
첫째 : String.Empty
관리되지 않는 코드에서 자동으로 초기화되도록 EE가 변경되었습니다 . 이 변경 사항은 .NET 4.0에서 변경된 것일 수 있습니다.
둘째 : 컴파일러 String.Empty
는 관리되지 않는 쪽에서 할당 될 것을 알고 문자열에 대한 정적 생성자를 방출하지 않도록 변경되었습니다 . 이 변경 사항은 .NET 4.5에 대한 것으로 보입니다.
EE 가String.Empty
일부 최적화 경로를 따라 충분히 빨리 할당 되지 않은 것으로 보입니다 . 컴파일러에 대한 변경 (또는 String.cctor
사라지 도록 변경 한 것 )은 EE가 사용자 코드가 실행되기 전에이 할당을 수행 할 것으로 예상했지만 EE가이 할당을 수행하지 않은 경우 String.Empty
참조 유형의 제네릭 클래스의 메소드에 사용됩니다.
마지막으로, 버그는 JIT 유형 초기화 논리에서 더 깊은 문제를 나타내는 것이라고 생각합니다. 컴파일러의 변경은 특별한 경우 System.String
이지만 JIT가 여기서 특별한 경우를 만든 것으로 의심됩니다 System.String
.
실물
우선, WOW BCL 직원은 일부 성능 최적화를 통해 매우 창의적이었습니다. 대부분 의 String
방법은 현재 스레드 정적 캐시 사용하여 수행됩니다 StringBuilder
개체를.
나는 그 리드를 잠시 따라 갔지만 코드 경로 StringBuilder
에는 사용되지 않았 Trim
으므로 스레드 정적 문제가 될 수 없다고 결정했습니다.
그래도 같은 버그가 이상한 것으로 나타났습니다.
이 코드는 액세스 위반으로 실패합니다.
class A<T>
{
static A() { }
public A(out string s) {
s = string.Empty;
}
}
class B
{
static void Main() {
string s;
new A<object>(out s);
//new A<int>(out s);
System.Console.WriteLine(s.Length);
}
}
주석이 경우, //new A<int>(out s);
에서 Main
다음 코드는 잘 작동합니다. 실제로, A
어떤 참조 유형으로도 통일되면 프로그램은 실패하지만, A
어떤 값 유형으로도 통일되면 코드는 실패하지 않습니다. 또한 A
정적 생성자 를 주석 처리 하면 코드가 실패하지 않습니다. Trim
and에 파고 들어 Format
문제가 Length
인라인되고 있으며 위의 샘플에서 String
유형이 초기화되지 않았다는 것이 분명합니다. 특히, 본체 내부 A
의 생성자 string.Empty
올바르게 비록 본체 내부에 할당되지 않은 Main
, string.Empty
바르게 할당된다.
String
어떻게 든 유형 초기화가 A
값 유형 으로 조정되는지 여부에 달려 있다는 것이 놀랍습니다 . 저의 유일한 이론은 모든 유형간에 공유되는 일반 유형 초기화를위한 최적화 된 JIT 코드 경로가 있으며 해당 경로는 BCL 참조 유형 ( “특별 유형”)과 해당 상태에 대한 가정을한다는 것입니다. 다른 BCL 클래스하지만 얼핏 public static
기본적으로 필드 쇼 모든 이들의 정적 생성자 (심지어 같은 빈 생성자없이 데이터로 구현 System.DBNull
하고 System.Empty
와. BCL 값 유형 public static
필드는 정적 생성자 (구현하지 않는 것 System.IntPtr
) 예를 들어, 이것은 JIT가 BCL 참조 유형 초기화에 대해 몇 가지 가정을한다는 것을 나타냅니다.
참고로 두 버전의 JIT 코드는 다음과 같습니다.
A<object>.ctor(out string)
:
public A(out string s) {
00000000 push rbx
00000001 sub rsp,20h
00000005 mov rbx,rdx
00000008 lea rdx,[FFEE38D0h]
0000000f mov rcx,qword ptr [rcx]
00000012 call 000000005F7AB4A0
s = string.Empty;
00000017 mov rdx,qword ptr [FFEE38D0h]
0000001e mov rcx,rbx
00000021 call 000000005F661180
00000026 nop
00000027 add rsp,20h
0000002b pop rbx
0000002c ret
}
A<int32>.ctor(out string)
:
public A(out string s) {
00000000 sub rsp,28h
00000004 mov rax,rdx
s = string.Empty;
00000007 mov rdx,12353250h
00000011 mov rdx,qword ptr [rdx]
00000014 mov rcx,rax
00000017 call 000000005F691160
0000001c nop
0000001d add rsp,28h
00000021 ret
}
나머지 코드 ( Main
)는 두 버전간에 동일합니다.
편집하다
또한 두 버전의 IL은 A.ctor
in 에 대한 호출을 제외하고 동일합니다 B.Main()
. 여기서 첫 번째 버전의 IL에는 다음이 포함됩니다.
newobj instance void class A`1<object>::.ctor(string&)
대
... A`1<int32>...
두 번째.
주목해야 할 또 다른 사항은 A<int>.ctor(out string)
:에 대한 JITed 코드 가 제네릭이 아닌 버전과 동일 하다는 것 입니다.
답변
난 강력하게이 발생 의심 (관련이 최적화 BeforeFieldInit
) .NET 4.0 인치
내가 정확하게 기억한다면 :
정적 생성자를 명시 적으로 선언하면 정적 멤버 액세스 전에beforefieldinit
정적 생성자 를 실행해야한다고 런타임에 알립니다 .
내 추측:
나는 그들이 어떻게 든 64의 JITer에이 사실을 망친 것을 추측에는 요 그래서 그 때 다른 유형의 정적 멤버는 그 클래스에서 액세스 자신의 정적 생성자 이미 실행하고있다, 어떻게 든 건너 뜁니다 (잘못된 순서로 또는 실행하는)를 실행하는 정적 생성자이므로 충돌이 발생합니다. (당신은 널 포인터 예외가 발생하지 않습니다 아마 이 null로 초기화되지 때문에.)
나는 한 하지 이 부분이 잘못 될 수 있도록, 당신의 코드를 실행 -하지만 난 또 다른 추측을해야한다면, 나는 뭔가 수 있습니다 말하고 싶지만 string.Format
(또는 Console.WriteLine
내부에서의이 같은 충돌을 일으키는 것을 유사)에 액세스해야 아마도 명시적인 정적 구성이 필요한 로케일 관련 클래스 일 것입니다.
다시 한 번 테스트하지는 않았지만 데이터를 가장 잘 추측합니다.
내 가설을 자유롭게 테스트하고 어떻게 진행되는지 알려주십시오.
답변
관찰이지만 DotPeek는 디 컴파일 된 문자열을 보여줍니다.
/// <summary>
/// Represents the empty string. This field is read-only.
/// </summary>
/// <filterpriority>1</filterpriority>
[__DynamicallyInvokable]
public static readonly string Empty;
internal sealed class __DynamicallyInvokableAttribute : Attribute
{
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public __DynamicallyInvokableAttribute()
{
}
}
Empty
속성이없는 것을 제외하고는 같은 방식으로 나 자신을 선언하면 더 이상 MDA를 얻지 못합니다.
class A<T>
{
static readonly string Empty;
static A() { }
public A()
{
string.Format("{0}", Empty);
}
}