2013-03-07 58 views
16

再次受到-5問題的啓發!什麼是關於switch-statement的編譯器思考?

我讀的@Quartermeister [this comment]和已經驚訝!

那麼,爲什麼這個編譯

switch(1) { 
    case 2: 
} 

但這並不。

int i; 

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

這也不

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

更新:

-5問題變得-3

+1

編譯器可能會優化第一個,但由於賦值而無法刪除第二個。 – 2013-03-07 20:34:51

+0

我的猜測是'1'是編譯時已知的一個常量表達式,讓編譯器優化整個開關,而'i = 1'是一個非常量表達式(儘管編譯器也知道產生一個特定的值),所以編譯器試圖保持開關。 – dasblinkenlight 2013-03-07 20:35:39

+0

在理想的世界中,編譯器可能應該接受或拒絕這兩者。它當然不應該爲它們生成任何代碼。但在這個世界上,我認爲這只是「阿甘正傳效應的另一個例子:」愚蠢是愚蠢的「:) Q:爲什麼浪費時間/ braincells甚至討論它? – paulsm4 2013-03-07 20:37:40

回答

21

沒有一個應該編譯。 C#規範要求開關部分至少有一條語句。解析器應該禁止它。

讓我們忽略解析器允許空語句列表的事實;這不是什麼相關的。該規範指出,切換部分的結尾不得有可到達的終點;這是相關的一點。

在你的最後一個例子,開關部分具有到達終點:

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

所以它必須是一個錯誤。

如果您有:

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

那麼編譯器不知道如果x將永遠是2.假設保守,它可以,並說,該部具有到達終點,因爲開關的情況下標籤是可達的。

如果你有

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

那麼編譯器可以推論端點不可達因爲案件標籤不可達。編譯器知道該常數1是從不等於常數2

如果您有:

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

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

然後,你知道,我知道終點不在可達,但編譯器不知道。規範中的規則是可達性只能通過分析常量表達式來確定。任何包含變量的表達式,即使通過其他方式知道它的值,也不是一個常量表達式。

在過去的C#編譯器有錯誤的地方,情況並非如此。你可以說這樣的話:

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

,編譯器會有理由相信X * 0必須是0,因此,本案的標籤是不可達。那是我在C#3.0中修復的一個錯誤。該規範說只有常數用於該分析,而x是一個變量,而不是一個常數。

現在,如果程序是合法那麼編譯器可以使用像這樣的高級技術來影響生成的代碼。如果你這樣說:

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

那麼編譯器生成代碼,就像你寫

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

如果它想。但是它不能使用x * 0 == 0爲確定語句可達性的事實。

最後,如果你有

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

那麼我們知道,開關不可達,因此該塊不具有到達終點,所以這是令人驚訝的,合法的。但考慮到上面的討論,現在你知道

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

不把x * 0 != 0false,所以終點認爲可以達到的。

+0

謝謝。我測試了'switch(x){}',它編譯,不是嗎? – 2013-03-07 23:49:39

+0

@KenKin:這是合法的;允許開關包含零開關部分。 – 2013-03-08 00:06:29

1

好了,所以這裏的問題是,編譯器完全優化掉開關,和這裏的證明:

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

    switch (1) 
    { 
     case 2: 
    } 

    Console.WriteLine("After!"); 
} 

,當與ILSpy反編譯,我們顯示了這種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 

其中任何地方都沒有回憶switch語句。我想認爲,它沒有優化掉第二個的原因可能與操作符重載和排序有關。所以,我可能有一個自定義類型,當它被分配到1時,它會變成2。但是,我並不完全確定,在我看來應該像提交bug報告一樣。

+0

「...優化了循環,」...什麼循環?我認爲你的意思是轉換。 – 2013-03-07 21:20:53

+0

@JimMischel好點,固定。 – 2013-03-07 21:36:26

+2

這不是一個錯誤;請不要提交報告。 – 2013-03-07 21:45:16

2

在Visual Studio 2012中,第一個的原因很明顯。編譯器確定代碼無法訪問:

switch (1) 
{ 
    case 2: 
} 

警告:檢測到無法訪問的代碼。

在另外兩種情況下,編譯器報告「控件不能從一個案例標籤('案例2:')到另一個案件」。我沒有看到它在任何一個失敗的案例中都說「('case 1')」。

我猜測編譯器對於不斷的評估並不積極。例如,下面的是等價的:

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

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

在這兩種情況下,編譯器試圖生成代碼,當它可以做了評價,並確定你在寫什麼是:

switch (1) 
{ 
    case 2: 
} 

並確定代碼無法訪問。

我懷疑「爲什麼不編譯」答案是「因爲我們讓JIT編譯器處理激進的優化」。

+0

謝謝。我修改了。 – 2013-03-07 23:30:32