visual varias trucos studio seleccionar salir multi modificar lineas linea explotarlo editar como code bloques bloque atributos ajuste activar c# integer-arithmetic

c# - trucos - seleccionar varias lineas visual studio code



¿Hay un costo para ingresar y salir de un bloque marcado con C#? (4)

Considere un bucle como este:

for (int i = 0; i < end; ++i) // do something

Si sé que no desbordaré, pero quiero un chequeo contra desbordamiento, truncamiento, etc., en la parte de "hacer algo", ¿estoy mejor con el bloque checked dentro o fuera del bucle?

for (int i = 0; i < end; ++i) checked { // do something }

o

checked { for (int i = 0; i < end; ++i) // do something }

En términos más generales, ¿existe un costo por cambiar entre el modo activado y no activado?


" En términos más generales, ¿hay un costo por cambiar entre el modo activado y no activado? "

No, no en tu ejemplo. La única sobrecarga es la ++i .

En ambos casos, el compilador de C # generará add.ovf , sub.ovf , mul.ovf o conv.ovf .

Pero cuando el bucle está dentro del bloque marcado, habrá un add.ovf adicional para ++i


Además de las respuestas anteriores, quiero aclarar cómo se realiza la verificación. El único método que conozco es verificar los indicadores OF y CF El indicador CF se establece mediante instrucciones aritméticas no firmadas, mientras que el OF se establece mediante instrucciones aritméticas firmadas.

Estas banderas se pueden leer con las instrucciones de seto/setc o (la forma más utilizada) solo podemos usar la instrucción de salto jo/jc que saltará a la dirección deseada si se establece la bandera OF/CF

Pero, hay un problema. jo/jc es un salto "condicional", que es un dolor total en el *** para la tubería de la CPU. Así que pensé que podría haber otra forma de hacerlo, como establecer un registro especial para interrumpir la ejecución cuando se detecta un desbordamiento, así que decidí averiguar cómo lo hace el JIT de Microsoft.

Estoy seguro de que la mayoría de ustedes escucharon que Microsoft ha abierto el subconjunto de .NET que se llama .NET Core. El código fuente de .NET Core incluye CoreCLR, así que lo busqué. El código de detección de desbordamiento se genera en el CodeGen::genCheckOverflow(GenTreePtr tree) (línea 2484). Se puede ver claramente que la instrucción jo se utiliza para la comprobación del desbordamiento firmado y la jb (¡sorpresa!) Para el desbordamiento sin firma. No he programado el ensamblaje durante mucho tiempo, pero parece que jb y jc son las mismas instrucciones (ambas solo verifican el indicador de acarreo). No sé por qué los desarrolladores de JIT decidieron usar jb lugar de jc porque, si yo fuera un fabricante de CPU, haría que un predictor de bifurcación suponga que los saltos de jo/jc es muy improbable.

En resumen, no se invocan instrucciones adicionales para cambiar entre el modo activado y no activado, pero las operaciones aritméticas en el bloque checked deben ser notablemente más lentas, siempre que la verificación se realice después de cada instrucción aritmética. Sin embargo, estoy bastante seguro de que las CPU modernas pueden manejar esto bien.

Espero que ayude.


Si realmente quieres ver la diferencia, echa un vistazo a algunos IL generados. Tomemos un ejemplo muy simple:

using System; public class Program { public static void Main() { for(int i = 0; i < 10; i++) { var b = int.MaxValue + i; } } }

Y obtenemos:

.maxstack 2 .locals init (int32 V_0, int32 V_1, bool V_2) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: br.s IL_0013 IL_0005: nop IL_0006: ldc.i4 0x7fffffff IL_000b: ldloc.0 IL_000c: add IL_000d: stloc.1 IL_000e: nop IL_000f: ldloc.0 IL_0010: ldc.i4.1 IL_0011: add IL_0012: stloc.0 IL_0013: ldloc.0 IL_0014: ldc.i4.s 10 IL_0016: clt IL_0018: stloc.2 IL_0019: ldloc.2 IL_001a: brtrue.s IL_0005 IL_001c: ret

Ahora, asegurémonos de que estamos revisados:

public class Program { public static void Main() { for(int i = 0; i < 10; i++) { checked { var b = int.MaxValue + i; } } } }

Y ahora obtenemos la siguiente IL:

.maxstack 2 .locals init (int32 V_0, int32 V_1, bool V_2) IL_0000: nop IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: br.s IL_0015 IL_0005: nop IL_0006: nop IL_0007: ldc.i4 0x7fffffff IL_000c: ldloc.0 IL_000d: add.ovf IL_000e: stloc.1 IL_000f: nop IL_0010: nop IL_0011: ldloc.0 IL_0012: ldc.i4.1 IL_0013: add IL_0014: stloc.0 IL_0015: ldloc.0 IL_0016: ldc.i4.s 10 IL_0018: clt IL_001a: stloc.2 IL_001b: ldloc.2 IL_001c: brtrue.s IL_0005 IL_001e: ret

Como puede ver, la única diferencia (con la excepción de algunos nop s adicionales) es que nuestra operación de agregar emite add.ovf lugar de un simple add . La única sobrecarga que acumulará es la diferencia en esas operaciones.

Ahora, ¿qué sucede si movemos el bloque checked para incluir todo el bucle for :

public class Program { public static void Main() { checked { for(int i = 0; i < 10; i++) { var b = int.MaxValue + i; } } } }

Obtenemos la nueva IL:

.maxstack 2 .locals init (int32 V_0, int32 V_1, bool V_2) IL_0000: nop IL_0001: nop IL_0002: ldc.i4.0 IL_0003: stloc.0 IL_0004: br.s IL_0014 IL_0006: nop IL_0007: ldc.i4 0x7fffffff IL_000c: ldloc.0 IL_000d: add.ovf IL_000e: stloc.1 IL_000f: nop IL_0010: ldloc.0 IL_0011: ldc.i4.1 IL_0012: add.ovf IL_0013: stloc.0 IL_0014: ldloc.0 IL_0015: ldc.i4.s 10 IL_0017: clt IL_0019: stloc.2 IL_001a: ldloc.2 IL_001b: brtrue.s IL_0006 IL_001d: nop IL_001e: ret

Puede ver que ambas operaciones de add se han convertido a add.ovf lugar de solo la operación interna, por lo que está obteniendo el doble de "gastos generales". En cualquier caso, supongo que la "sobrecarga" sería despreciable para la mayoría de los casos de uso.


checked bloques unchecked y unchecked marcar no aparecen en el nivel de IL. Solo se usan en el código fuente de C # para decirle al compilador si debe o no elegir las instrucciones de IL que no están verificando o no al anular la preferencia predeterminada de la configuración de compilación (que se establece a través de un indicador de compilador).

Por supuesto, normalmente habrá una diferencia de rendimiento debido al hecho de que se han emitido diferentes códigos de operación para las operaciones aritméticas (pero no debido a la entrada o salida del bloque). En general, se espera que la aritmética verificada tenga alguna sobrecarga sobre la aritmética no verificada correspondiente.

De hecho, considere este programa C #:

class Program { static void Main(string[] args) { var a = 1; var b = 2; int u1, c1, u2, c2; Console.Write("unchecked add "); unchecked { u1 = a + b; } Console.WriteLine(u1); Console.Write("checked add "); checked { c1 = a + b; } Console.WriteLine(c1); Console.Write("unchecked call "); unchecked { u2 = Add(a, b); } Console.WriteLine(u2); Console.Write("checked call "); checked { c2 = Add(a, b); } Console.WriteLine(c2); } static int Add(int a, int b) { return a + b; } }

Esta es la IL generada, con las optimizaciones activadas y con aritmética desactivada de forma predeterminada:

.class private auto ansi beforefieldinit Checked.Program extends [mscorlib]System.Object { .method private hidebysig static int32 Add ( int32 a, int32 b ) cil managed { IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: add IL_0003: ret } .method private hidebysig static void Main ( string[] args ) cil managed { .entrypoint .locals init ( [0] int32 b ) IL_0000: ldc.i4.1 IL_0001: ldc.i4.2 IL_0002: stloc.0 IL_0003: ldstr "unchecked add " IL_0008: call void [mscorlib]System.Console::Write(string) IL_000d: dup IL_000e: ldloc.0 IL_000f: add IL_0010: call void [mscorlib]System.Console::WriteLine(int32) IL_0015: ldstr "checked add " IL_001a: call void [mscorlib]System.Console::Write(string) IL_001f: dup IL_0020: ldloc.0 IL_0021: add.ovf IL_0022: call void [mscorlib]System.Console::WriteLine(int32) IL_0027: ldstr "unchecked call " IL_002c: call void [mscorlib]System.Console::Write(string) IL_0031: dup IL_0032: ldloc.0 IL_0033: call int32 Checked.Program::Add(int32, int32) IL_0038: call void [mscorlib]System.Console::WriteLine(int32) IL_003d: ldstr "checked call " IL_0042: call void [mscorlib]System.Console::Write(string) IL_0047: ldloc.0 IL_0048: call int32 Checked.Program::Add(int32, int32) IL_004d: call void [mscorlib]System.Console::WriteLine(int32) IL_0052: ret } }

Como puede ver, los bloques unchecked y unchecked son simplemente un concepto de código fuente: no se emite IL cuando se alterna entre lo que era (en la fuente) un contexto checked y unchecked . Lo que cambia son los códigos de operación emitidos para las operaciones aritméticas directas (en este caso, add y add.ovf ) que se add.ovf textualmente en esos bloques. La especificación cubre qué operaciones se ven afectadas:

Las siguientes operaciones se ven afectadas por el contexto de verificación de desbordamiento establecido por los operadores y declaraciones verificados y no verificados:

  • Los operadores predefinidos ++ y - unarios (§7.6.9 y §7.7.5), cuando el operando es de un tipo integral.
  • El operador predefinido - unario (§7.7.2), cuando el operando es de un tipo integral.
  • Los operadores predefinidos +, -, * y / binarios (§7.8), cuando ambos operandos son de tipos integrales.
  • Conversiones numéricas explícitas (§6.2.1) de un tipo integral a otro tipo integral, o de tipo flotante o doble a un tipo integral.

Y como puede ver, un método llamado desde un bloque checked o unchecked conservará su cuerpo y no recibirá ninguna información sobre el contexto desde el que se llamó. Esto también se explica en la especificación:

Los operadores marcados y no verificados solo afectan el contexto de verificación de desbordamiento para aquellas operaciones que están contenidas textualmente dentro de los tokens "(" y ")". Los operadores no tienen efecto en los miembros de la función que se invocan como resultado de evaluar la expresión contenida.

En el ejemplo

class Test { static int Multiply(int x, int y) { return x * y; } static int F() { return checked(Multiply(1000000, 1000000)); } }

el uso de checked en F no afecta la evaluación de x * y en Multiply, por lo que x * y se evalúa en el contexto de verificación de desbordamiento predeterminado.

Como se señaló, la IL anterior se generó con las optimizaciones del compilador C # activadas. Las mismas conclusiones pueden extraerse de la IL que se emite sin estas optimizaciones.