El código de muestra a continuación ocurrió naturalmente. De repente, mi código fue una FatalExecutionEngineError
excepción muy desagradable . Pasé unos 30 minutos tratando de aislar y minimizar la muestra culpable. Compile esto usando Visual Studio 2012 como una aplicación de consola:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Debería producir este error en .NET Framework 4 y 4.5:
¿Es este un error conocido, cuál es la causa y qué puedo hacer para mitigarlo? Mi trabajo actual es no usar string.Empty
, pero ¿estoy ladrando el árbol equivocado? Cambiar cualquier cosa sobre ese código hace que funcione como cabría esperar, por ejemplo, eliminar el constructor estático vacío de A
, o cambiar el parámetro de tipo de object
a int
.
Probé este código en mi computadora portátil y no se quejó. Sin embargo, probé mi aplicación principal y también se estrelló en la computadora portátil. Debo haber destrozado algo al reducir el problema, veré si puedo descubrir qué fue eso.
Mi computadora portátil se bloqueó con el mismo código que el anterior, con Framework 4.0, pero el principal se bloquea incluso con 4.5. Ambos sistemas están utilizando VS'12 con las últimas actualizaciones (¿julio?).
Más información :
- Código IL (depuración compilada / Cualquier CPU / 4.0 / VS2010 (¿no debería importar el IDE?)): Http://codepad.org/boZDd98E
- No visto VS 2010 con 4.0. No se bloquea con / sin optimizaciones, CPU de destino diferente, depurador adjunto / no conectado, etc. - Tim Medora
- Se bloquea en 2010 si uso AnyCPU, está bien en x86. Se bloquea en Visual Studio 2010 SP1, utilizando Platform Target = AnyCPU, pero está bien con Platform Target = x86. Esta máquina también tiene VS2012RC instalado, por lo que 4.5 posiblemente realice un reemplazo en el lugar. Use AnyCPU y TargetPlatform = 3.5, entonces no se bloquea, por lo que parece una regresión en el Framework .
- No se puede reproducir en x86, x64 o AnyCPU en VS2010 con 4.0. - Fuji
- Solo sucede para x64, (2012rc, Fx4.5) - Henk Holterman
- VS2012 RC en Win8 RP. Inicialmente No veo este MDA cuando se dirige a .NET 4.5. Cuando se cambió a apuntar a .NET 4.0, apareció la MDA. Luego, después de volver a .NET 4.5, el MDA permanece. - Wayne
Respuestas:
Esta tampoco es una respuesta completa, pero tengo algunas ideas.Creo que he encontrado una explicación tan buena como la que encontraremos sin que alguien del equipo de .NET JIT responda.
ACTUALIZAR
Miré un poco más profundo y creo que he encontrado la fuente del problema. Parece ser causado por una combinación de un error en la lógica de inicialización de tipo JIT y un cambio en el compilador de C # que se basa en la suposición de que el JIT funciona según lo previsto. Creo que el error JIT existía en .NET 4.0, pero fue descubierto por el cambio en el compilador para .NET 4.5.
No creo que
beforefieldinit
sea el único problema aquí. Creo que es más simple que eso.El tipo
System.String
en mscorlib.dll de .NET 4.0 contiene un constructor estático:En la versión .NET 4.5 de mscorlib.dll,
String.cctor
(el constructor estático) está notablemente ausente:En ambas versiones, el
String
tipo está adornado conbeforefieldinit
:Traté de crear un tipo que compilara a IL de manera similar (para que tenga campos estáticos pero sin constructor estático
.cctor
), pero no pude hacerlo. Todos estos tipos tienen un.cctor
método en IL:Supongo que dos cosas cambiaron entre .NET 4.0 y 4.5:
Primero: el EE se cambió para que se inicializara automáticamente
String.Empty
desde el código no administrado. Este cambio probablemente se realizó para .NET 4.0.Segundo: el compilador cambió para que no emitiera un constructor estático para la cadena, sabiendo que
String.Empty
se asignaría desde el lado no administrado. Este cambio parece haberse realizado para .NET 4.5.Parece que EE no asigna lo
String.Empty
suficientemente pronto a lo largo de algunas rutas de optimización. El cambio realizado en el compilador (o lo que sea que haya cambiado para hacerString.cctor
desaparecer) esperaba que EE realizara esta asignación antes de que se ejecute cualquier código de usuario, pero parece que EE no realiza esta asignación antes de queString.Empty
se use en métodos de clases genéricas reificadas de tipo de referencia.Por último, creo que el error es indicativo de un problema más profundo en la lógica de inicialización de tipo JIT. Parece que el cambio en el compilador es un caso especial para
System.String
, pero dudo que el JIT haya hecho un caso especial aquí paraSystem.String
.Original
En primer lugar, WOW La gente de BCL se ha vuelto muy creativa con algunas optimizaciones de rendimiento. Muchos de los
String
métodos ahora se realizan utilizando unStringBuilder
objeto en caché estático Thread .Seguí esa pista por un tiempo, pero
StringBuilder
no se usa en laTrim
ruta del código, así que decidí que no podría ser un problema estático de Thread.Sin embargo, creo que encontré una extraña manifestación del mismo error.
Este código falla con una infracción de acceso:
Sin embargo, si usted elimine el comentario
//new A<int>(out s);
deMain
entonces el código funciona bien. De hecho, siA
se reifica con cualquier tipo de referencia, el programa falla, pero siA
se reifica con cualquier tipo de valor, entonces el código no falla. Además, si comentaA
el constructor estático, el código nunca falla. Después de profundizar enTrim
yFormat
, está claro que el problema es queLength
se está alineando, y que en estas muestras anteriores, elString
tipo no se ha inicializado. En particular, dentro del cuerpo delA
constructor,string.Empty
no está asignado correctamente, aunque dentro del cuerpo deMain
,string.Empty
está asignado correctamente.Es sorprendente para mí que la inicialización de tipo de
String
alguna manera dependa de siA
se reifica o no con un tipo de valor. Mi única teoría es que hay una ruta de código de optimización JIT para la inicialización de tipo genérica que se comparte entre todos los tipos, y que esa ruta hace suposiciones sobre los tipos de referencia BCL ("¿tipos especiales?") Y su estado. Un vistazo rápido a otras clases de BCL conpublic static
campos muestra que básicamente todas ellas implementan un constructor estático (incluso aquellos con constructores vacíos y sin datos, comoSystem.DBNull
ySystem.Empty
. Los tipos de valores BCL conpublic static
campos no parecen implementar un constructor estático (System.IntPtr
por ejemplo) Esto parece indicar que el JIT hace algunas suposiciones sobre la inicialización del tipo de referencia BCL.FYI Aquí está el código JITed para las dos versiones:
A<object>.ctor(out string)
:A<int32>.ctor(out string)
:El resto del código (
Main
) es idéntico entre las dos versiones.EDITAR
Además, el IL de las dos versiones es idéntico, excepto por la llamada a
A.ctor
inB.Main()
, donde el IL de la primera versión contiene:versus
en el segundo.
Otra cosa a tener en cuenta es que el código JITed para
A<int>.ctor(out string)
: es el mismo que en la versión no genérica.fuente
string.Empty
o""
... :)typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);
da resultados diferentes en .NET 4.0 frente a .NET 4.5. ¿Está este cambio relacionado con el cambio descrito anteriormente? ¿Cómo puede .NET 4.5 técnicamente ignorarme cambiando un valor de campo? ¿Tal vez debería hacer una nueva pregunta sobre esto?Sospecho firmemente que esto es causado por esta optimización (relacionada con
BeforeFieldInit
) en .NET 4.0.Si recuerdo correctamente:
Cuando declara un constructor estático explícitamente,
beforefieldinit
se emite, indicando al tiempo de ejecución que el constructor estático debe ejecutarse antes de que cualquier miembro estático acceda .Mi conjetura:
Supongo que de alguna manera arruinaron este hecho en el x64 JITer, de modo que cuando se accede a un miembro estático de un tipo diferente desde una clase cuyo propio constructor estático ya se ha ejecutado, de alguna manera omite la ejecución (o se ejecuta en el orden incorrecto) constructor estático y, por lo tanto, provoca un bloqueo. (No obtiene una excepción de puntero nulo, probablemente porque no está inicializado por nulo).
He no ejecutar el código, por lo que esta parte puede estar equivocado - pero si tuviera que hacer otra conjetura, diría que podría ser algo
string.Format
(oConsole.WriteLine
, lo que es similar) necesita acceso interno que está causando el accidente, tales como tal vez una clase relacionada con el entorno local que necesita una construcción estática explícita.Nuevamente, no lo he probado, pero es mi mejor conjetura de los datos.
Siéntase libre de probar mi hipótesis y hágame saber cómo va.
fuente
B
no tiene un constructor estático, y no ocurre cuandoA
se reifica con un tipo de valor. Creo que es un poco más complicado.B
Tener un constructor estático no importa mucho. ComoA
tiene un ctor estático, el tiempo de ejecución desordena el orden en que se ejecuta cuando se compara con alguna clase relacionada con la configuración regional en algún otro espacio de nombres. Entonces ese campo aún no se ha inicializado. Sin embargo, si creaA
una instancia con un tipo de valor, entonces podría ser el segundo paso del tiempo de ejecución a través de la creación de instanciasA
(es probable que el CLR ya lo haya instanciado previamente con un tipo de referencia, como una optimización) para que el orden funcione cuando se ejecuta por segunda vez .beforefieldinit
optimización dada es la causa raíz. Puede ser que parte de la explicación real sea diferente de lo que mencioné, pero la causa raíz es probablemente la misma.A<object>.ctor()
.Una observación, pero DotPeek muestra la cadena descompilada. Vacía así:
Si declaro el mío de
Empty
la misma manera, excepto sin el atributo, ya no obtengo el MDA:fuente
""
resuelve.