2013-03-07 8 views
16

Inspiriert von einer -5 Frage erneut!Was denkt der Compiler über die switch-Anweisung?

Ich las [this comment] von @Quartermeister und erstaunt gewesen!

Warum diese kompiliert

switch(1) { 
    case 2: 
} 

aber dies nicht der Fall. weder

int i; 

switch(i=1) { 
    case 2: // Control cannot fall through from one case label ('case 2:') to another 
} 

diese

switch(2) { 
    case 2: // Control cannot fall through from one case label ('case 2:') to another 
} 

Update:

Die -5 Frage wurde -3.

+1

Es ist möglich, dass der Compiler die erste optimieren kann, aber die zweite wegen der Zuweisung nicht entfernen kann. –

+0

Meine Vermutung ist, dass '1' ein zur Kompilierungszeit bekannter konstanter Ausdruck ist, der den Compiler den gesamten Schalter optimieren lässt, während 'i = 1' ein nicht konstanter Ausdruck ist (obwohl er dem Compiler auch bekannt ist, um einen bestimmten Wert zu erzeugen), so versucht der Compiler den Schalter zu halten. – dasblinkenlight

+0

In einer idealen Welt sollte der Compiler beide akzeptieren oder ablehnen. Es sollte sicherlich keinen Code für sie generieren. Aber in dieser Welt denke ich, es ist nur ein weiteres Beispiel für "The Forrest Gump-Effekt:" Dumm ist so dumm tut ":) F: Warum Zeit verschwenden? Braincells sogar diskutieren? – paulsm4

Antwort

21

Keine von ihnen sollte kompilieren. Die C# -Spezifikation erfordert, dass ein Switch-Abschnitt mindestens eine Anweisung aufweist. Der Parser sollte es ablehnen.

Lassen Sie uns die Tatsache ignorieren, dass der Parser eine leere Anweisungsliste erlaubt; das ist nicht relevant. Die Spezifikation besagt, dass das Ende des Switch-Abschnitts keinen erreichbaren Endpunkt haben darf; das ist das relevante bisschen.

In Ihrem letzten Beispiel hat der Schalterabschnitt einen erreichbaren Endpunkt:

void M(int x) { switch(2) { case 2: ; } } 

so muss es ein Fehler sein.

Wenn Sie hat:

void M(int x) { switch(x) { case 2: ; } } 

dann der Compiler nicht weiß, ob x je 2 wird Sie geht davon aus, dass es konservativ konnte, und sagt, dass der Abschnitt einen erreichbaren Endpunkt hat, da das Schaltergehäuse Label ist erreichbar.

Wenn Sie

hatte
void M(int x) { switch(1) { case 2: ; } } 

Dann kann der Compiler Grund dafür, dass der Endpunkt nicht erreichbar ist, weil der Fall Etikett nicht erreichbar ist. Der Compiler weiß, dass die Konstante 1 ist nie gleich der konstanten 2.

Wenn Sie hatte:

void M(int x) { switch(x = 1) { case 2: ; } } 

oder

void M(int x) { x = 1; switch(x) { case 2: ; } } 

Dann wissen Sie, und ich weiß, dass der Endpunkt nicht erreichbar, aber der Compiler weiß das nicht. Die Regel in der Spezifikation besagt, dass die Erreichbarkeit nur durch Analyse konstanter Ausdrücke bestimmt wird. Jeder Ausdruck, der eine Variable enthält, auch wenn Sie ihren Wert auf andere Weise kennen, ist kein konstanter Ausdruck.

In der Vergangenheit hatte der C# -Compiler Fehler, wo dies nicht der Fall war. Man könnte sagen Dinge wie:

void M(int x) { switch(x * 0) { case 2: ; } } 

und der Compiler würde vermuten, dass x * 0 hatte 0 sein, deshalb der Fall Etikett nicht erreichbar ist. Das war ein Fehler, den ich in C# 3.0 behoben habe. Die Spezifikation besagt, dass nur Konstanten für diese Analyse verwendet werden, und x ist eine Variable, keine Konstante.

Jetzt, wenn das Programm legal ist, kann der Compiler fortgeschrittene Techniken wie diese verwenden, um zu beeinflussen, welcher Code generiert wird. Wenn Sie sagen, so etwas wie:

void M(int x) { if (x * 0 == 0) Y(); } 

Dann kann der Compiler den Code generieren, als ob Sie

void M(int x) { Y(); } 

geschrieben hatte, wenn er will.Aber es kann nicht die Tatsache verwenden, dass x * 0 == 0 wahr ist, um die Erreichbarkeit von Aussagen zu bestimmen.

Schließlich, wenn Sie

void M(int x) { if (false) switch(x) { case 2: ; } } 

haben, dann wissen wir, dass der Schalter nicht erreichbar ist, also der Block keinen erreichbaren Endpunkt, so ist dies überraschenderweise legal. Aber angesichts der Diskussion über, Sie wissen jetzt, dass

void M(int x) { if (x * 0 != 0) switch(x) { case 2: ; } } 

nicht x * 0 != 0 als false nicht behandeln, so dass der Endpunkt erreichbar angesehen wird.

+0

Danke. Ich habe für 'switch (x) {}' getestet, es kompiliert, oder? –

+0

@KenKin: Das ist legal; Ein Schalter darf Nullschalterabschnitte enthalten. –

1

In Ordnung, so dass das Problem dabei ist, dass der Compiler vollständig den Schalter optimiert weg, und hier ist der Beweis:

static void withoutVar() 
{ 
    Console.WriteLine("Before!"); 

    switch (1) 
    { 
     case 2: 
    } 

    Console.WriteLine("After!"); 
} 

die, wenn sie mit ILSpy dekompilierten, zeigt uns diese IL:

.method private hidebysig static 
    void withoutVar() cil managed 
{ 
    // Method begins at RVA 0x2053 
    // Code size 26 (0x1a) 
    .maxstack 8 

    IL_0000: nop 
    IL_0001: ldstr "Before!" 
    IL_0006: call void [mscorlib]System.Console::WriteLine(string) 
    IL_000b: nop 
    IL_000c: br.s IL_000e 

    IL_000e: ldstr "After!" 
    IL_0013: call void [mscorlib]System.Console::WriteLine(string) 
    IL_0018: nop 
    IL_0019: ret 
} // end of method Program::withoutVar 

Das hat keine Erinnerung an eine switch-Anweisung irgendwo. I denke,, dass der Grund, warum es nicht die zweite weg optimieren auch möglicherweise etwas mit Operator Überladung und die Art zu tun haben. Also könnte es möglich sein, dass ich einen benutzerdefinierten Typ habe, der bei Zuweisung zu 1 in 2 umgewandelt wird. Allerdings bin ich mir nicht ganz sicher, scheint mir wie ein Bugreport einzureichen.

+0

"... optimiert die Schleife," ... welche Schleife? Ich denke, du meintest wechseln. –

+0

@JimMischel guter Punkt, behoben. –

+2

Es ist kein Fehler; Bitte reichen Sie keinen Bericht ein. –

2

In Visual Studio 2012 ist der Grund für die erste offensichtlich. Der Compiler stellt fest, dass der Code nicht erreichbar ist:

switch (1) 
{ 
    case 2: 
} 

Warnung: Nicht erreichbarer Code erkannt.

In den beiden anderen Fällen meldet der Compiler "Kontrolle kann nicht von einer Fallbezeichnung ('Fall 2:') zu einer anderen fallen". Ich sehe es nicht in jedem Fall "(Fall 1 ')".

Ich denke, der Compiler ist einfach nicht aggressiv über die konstante Auswertung. Zum Beispiel sind die folgenden äquivalent:

int i; 
switch(i=1) 
{ 
    case 2: 
} 

und

int i = 1; 
switch(i) 
{ 
    case 2: 
} 

In beiden Fällen versucht der Compiler Code zu generieren, wenn es die Auswertung tun könnte und bestimmen, dass, was Sie schreiben ist:

switch (1) 
{ 
    case 2: 
} 

Und feststellen, dass der Code nicht erreichbar ist.

Ich vermute, dass die Antwort "warum wird diese Kompilierung nicht" sein wird, "weil wir den JIT-Compiler aggressive Optimierung handhaben lassen."

+0

Danke. Ich habe überarbeitet. –

Verwandte Themen