Что компилятор думает о switch-statement? - программирование
Подтвердить что ты не робот

Что компилятор думает о switch-statement?

Вдохновленный снова с вопросом -5!

Я прочитал [этот комментарий] @Quartermeister и был поражен!

Итак, почему это компилирует

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
}

update:

Вопрос -5 стал -3.

4b9b3361

Ответ 1

Ни один из них не должен компилироваться. Спецификация С# требует, чтобы секция коммутатора имела хотя бы один оператор. Парсер должен запретить его.

Пусть игнорирует тот факт, что синтаксический анализатор допускает пустой список операторов; это не то, что уместно. В спецификации указано, что конец секции переключателя не должен иметь конечную точку достижимости; что соответствующий бит.

В вашем последнем примере раздел переключения имеет конечную точку достижимости:

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

поэтому это должна быть ошибка.

Если у вас есть:

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

тогда компилятор не знает, будет ли x когда-либо равным 2. Он предполагает консервативно, что он может, и говорит, что раздел имеет конечную точку достижимости, потому что метка case переключателя достижима.

Если у вас

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

Тогда компилятор может понять, что конечная точка недоступна, потому что метка case недоступна. Компилятор знает, что константа 1 никогда не равна константе 2.

Если у вас есть:

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

или

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

Тогда вы знаете, и я знаю, что конечная точка недоступна, но компилятор этого не знает. Правило в спецификации заключается в том, что достижимость определяется только анализом постоянных выражений. Любое выражение, которое содержит переменную, даже если вы знаете ее значение каким-либо другим способом, не является постоянным выражением.

В прошлом компилятор С# имел ошибки, когда это было не так. Вы могли бы сказать такие вещи, как:

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

и компилятор будет считать, что x * 0 должно быть 0, поэтому метка case недоступна. Это была ошибка, которую я исправил в С# 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 != 0 как false, поэтому конечная точка считается достижимой.

Ответ 2

В Visual Studio 2012 причина для первого очевидна. Компилятор определяет, что код недостижим:

switch (1)
{
    case 2:
}

Предупреждение: обнаружен недостижимый код.

В двух других случаях сообщения компилятора "Контроль не может проваливаться с одного ярлыка случая (" случай 2: ") на другой". Я не вижу, чтобы он говорил "(" случай 1 ") в любом из неудачных случаев.

Я думаю, что компилятор просто не агрессивен относительно постоянной оценки. Например, следующие эквиваленты:

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

и

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

В обоих случаях компилятор пытается сгенерировать код, когда он может выполнить оценку и определить, что вы пишете:

switch (1)
{
    case 2:
}

И определите, что код недостижим.

Я подозреваю, что ответ "почему это не компилируется" будет "потому что мы позволяем компилятору JIT обрабатывать агрессивную оптимизацию".

Ответ 3

Хорошо, поэтому проблема заключается в том, что компилятор полностью оптимизирует коммутатор, и здесь доказательство:

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. Тем не менее, я не совсем уверен, мне кажется, что отчет об ошибке должен быть представлен.