이것은 전체 답변이 아니지만 몇 가지 아이디어가 있습니다.
.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 코드 가 제네릭이 아닌 버전과 동일 하다는 것 입니다.