Как вносить switch case в массив си
Перейти к содержимому

Как вносить switch case в массив си

  • автор:

Тонкости оператора switch

Да, это целая статья по самому обычному switch в JDK 7. Бывает так, что накопленный материал кажется интересным и малоизвестным, а потом оказывается, что любая бабка у подъезда уже 50 лет знает об особенностях реализации switch. Но я попробую. Для затравки, предлагаю 3 вопроса:

    (Простой) Каков результат работы этого кода?

switch(5)
//Вариант 1 switch("BBBBBB")
//Вариант 2 switch("BBBBBB_8")
  1. 01
  2. Метод hashCode() возвращает одинаковое значение для всех строк первого switch. Подробности ниже.
  3. В зависимости от случая может быть O(1), O(log n) и даже достигать O(n).
TableSwitch

Возьмем пример из первой задачи. Скомпилируем его и дизассемблируем полученный байт-код.

javap -c Main.class
 0: iconst_5 // Засовываем 5 в стек 1: tableswitch < // 1 to 4 // Забираем 3 из стека и ищем в таблице 1: 39 // 39, 56, 32, 49 – адресные метки переходов 2: 56 3: 32 4: 49 default: 32 >32: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream; 35: iconst_0 36: invokevirtual #33 // Method java/io/PrintStream.print:(I)V 39: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream; 42: iconst_1 43: invokevirtual #33 // Method java/io/PrintStream.print:(I)V 46: goto 63 // break 49: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream; 52: iconst_4 53: invokevirtual #33 // Method java/io/PrintStream.print:(I)V 56: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream; 59: iconst_2 60: invokevirtual #33 // Method java/io/PrintStream.print:(I)V 63: return 

Нас особенно интересует инструкция с меткой 1. Она означает, что компилятор создал таблицу (массив), содержащий адреса переходов для всех значений ключей от наименьшего до наибольшего, без исключений. Алгоритм выполнения switch примерно такой (псевдокод):

if (valhigh)< jump default; >else

В нашем случае: low==1, high==4, table==. Так как в таблице должны быть последовательно все ключи от low до high, то компилятору пришлось добавить ключ 3 и задать для него то же поведение, что и для default.

Начиная с инструкции 32, код всех case и default в порядке их расположения в исходном коде. Грубо говоря, здесь компилятор генерирует сплошной код обработчиков ключей. Думаю, теперь понятно, почему после найденного соответствия, выполнение продолжается до первого встретившегося break.

LookupSwitch

Появляется резонный вопрос: а что если значения ключей сильно разрежены? Если у нас их всего два: 1 и 1000000, то крайне неразумно создавать массив с миллионом элементов. Заменим в нашем примере ключ 4 на 10, этого будет достаточно (если вдруг нет – увеличьте). Смотрим байт-код (байт-код обработчиков остался практически тем же, поэтому не приведен):

 1: lookupswitch < // 3 1: 43 2: 60 10: 53 default: 36 >

Здесь тоже задается таблица, но уже для пар: ключ — метка перехода. В спецификации JVM сказано, что хотя поиск может быть и линейным, ключи обязательно должны быть отсортированы для возможности более быстрого поиска, хотя сам способ поиска не оговаривается. Возможно, в каких-нибудь реализациях используется линейный поиск. Это первый известный мне случай (хотя и теоретический) со сложностью switch O(n). Далее мы увидим другой, более осязаемый.

Реальные пацаны и девчата спрашивают: как компилятор решает что выбрать – tableswitch или lookupswitch? А самые реальные скачивают исходники OpenJDK (учтите, в других реализациях JDK способ выбора может отличаться) и изучают метод visitSwitch класса com.sun.tools.javac.jvm.Gen.java в openjdk/langtools/src/share/classes.

 // Determine whether to issue a tableswitch or a lookupswitch // instruction. long table_space_cost = 4 + ((long) hi - lo + 1); // words long table_time_cost = 3; // comparisons long lookup_space_cost = 3 + 2 * (long) nlabels; long lookup_time_cost = nlabels; int opcode = nlabels > 0 && table_space_cost + 3 * table_time_cost  

table_space_cost – в этот размер входит количество всех значений диапазона, плюс по одному значению для lo, hi, default_address и маркер выбранного switch-метода (tableswitch).
table_time_cost – 3 операции: проверка вхождения в диапазон (на минимум и максимум) и извлечение адресной метки из таблицы.
lookup_space_cost – 2 значения на каждую пару ключ-адрес, плюс по значению для размера таблицы, default_address, и маркер выбранного switch-метода (lookupswitch).
lookup_time_cost – предполагается худший случай – линейный поиск.

А сам алгоритм, как видите, нехитрый. Магическое число 3 («И эти люди запрещают нам ковыряться в носу» (с)), скорее всего, эмпирическое.

Сравнение строк

«String.hashCode может запросто иметь коллизии, String.equals слишком медленен, может быть, строки интернируются?» – так думал я, пока не изучил байт-код.

switch("")
 0: ldc #27 // String 2: dup 3: astore_0 4: invokevirtual #29 // Method java/lang/String.hashCode:()I 7: lookupswitch < // 2 2080: 32 2112: 44 default: 73 >32: aload_0 33: ldc #35 // String AA 35: invokevirtual #37 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 38: ifne 56 41: goto 73 44: aload_0 45: ldc #41 // String BB 47: invokevirtual #37 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 50: ifne 66 53: goto 73 56: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream; 59: iconst_1 60: invokevirtual #49 // Method java/io/PrintStream.println:(I)V 63: goto 73 66: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream; 69: iconst_2 70: invokevirtual #49 // Method java/io/PrintStream.println:(I)V 73: return 

То есть, на этапе компиляции вычисляется хэш-код всех ключей. Для строк всегда выполняется lookupSwitch, даже если хэши последовательны. При выполнении вычисляется хэш-код условного выражения и именно он сравнивается с хэшами-ключами. При совпадении строки еще и сравниваются (адреса 32–53) методом String.equals() для предотвращения возможной коллизии. И, в случае успеха, переход к выполнению соответствующего выражения (56–70).

А если у нас несколько ключей с одинаковыми хэшами?

switch("")
 7: lookupswitch < // 1 2112: 24 default: 62 >24: aload_0 25: ldc #35 // String Aa 27: invokevirtual #37 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 30: ifne 45 33: aload_0 34: ldc #41 // String BB 36: invokevirtual #37 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 39: ifne 55 42: goto 62 45: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream; 48: iconst_1 49: invokevirtual #49 // Method java/io/PrintStream.println:(I)V 52: goto 62 55: getstatic #43 // Field java/lang/System.out:Ljava/io/PrintStream; 58: iconst_2 59: invokevirtual #49 // Method java/io/PrintStream.println:(I)V 62: return 

Тогда эти ключи объединяются под одним хэш-ключом в lookupswitch, и, при совпадении ключа, происходит перебор всех строк с этим хэшем и их сравнение с помощью String.equals(). Пример из 2-го вопроса выполняет аж 8 сравнений. Вот вам и второй случай со сложностью O(n).

Выводы

Если бы не JIT, то можно было бы порассуждать об оптимизации switch. Но мне не удалось подобрать хорошего примера, в котором tableswitch был бы заметно быстрее lookupswitch (с включенным JIT). Ну, разве что такой:
1. Допустим, у нас N ключей со значениями от 1 до N. В этом случае, будет использоваться tableswitch.
2. Те же самые ключи, но плюс еще один, со значением много большим N. В этом случае, будет использоваться lookupswitch.
(С отключенным JIT все понятно, разница ощутима.)
С JIT никакой разницы. Возможно, он разбивает все ключи на несколько диапазонов и поступает с ними аналогично tableswitch. Однако, начиная с N=721, у меня произошло резкое падение производительности второго примера.

В завершение, напрашиваются только совсем дикие советы, считайте их шуткой: «Ребята, если у вас в цикле, который должен выполняться сто миллионов раз в секунду, 1000 case-ов с последовательными ключами кроме нескольких, то обрабатывайте эти несколько ключей вне switch. А если в этом цикле куча строковых ключей с одинаковыми хэшами, то подумайте о других способах реализации».

Объявление внутри switch

Объявление переменной внутри цикла while
Доброго времени суток, форумчане. Расскажите нубу, что происходит при объявлении объявленной.

gets внутри switch
Подскажите, почему не получается осуществить gets для ввода char внутри данной конструкции: do .

155 / 142 / 62
Регистрация: 08.09.2014
Сообщений: 1,220
Кто тебе такое сказал

1 2 3 4 5
case true: { int g = 8; cout  g;} break;

А по твоему вопросу смотри синтаксис

1 2 3 4 5 6 7
switch (bool b = true) { case true: int g = 8; cout  g; break; } return 0;

Регистрация: 24.10.2015
Сообщений: 6

Выполнение оператора switch способно переходить через метки case. Когда выполнение переходит к некой метке case, весь расположенный выше код оператора switch будет проигнорирован. Что будет, если пропущен код с объявленной переменной?
Поэтому, недопустим переход с места, где переменная с инициализатором вышла из области видимости к месту, где эта переменная находится в области видимости.

1 2 3 4 5 6 7
case true: int j = 0; int i = 0; break; case false: i = 1; // ок в области видимости j = 1; // ок в области видимости

если данный код будет доступен, и мы пропускаем секцию true, следовательно мы обходим объявления переменных со всеми вытекающими.

1370 / 593 / 199
Регистрация: 02.08.2011
Сообщений: 2,882

ЦитатаСообщение от zelhat Посмотреть сообщение

где переменная с инициализатором вышла из области видимости
Как что-то может выйти из области видимости в case, если:

ЦитатаСообщение от DrOffset Посмотреть сообщение

Метка case не вносит области видимости же
Регистрация: 24.10.2015
Сообщений: 6

1 2 3 4 5 6
case true: int j = 0; int i = 0; break; case false: i = j;

Если бы данный код был допустим, переход ко второму case обходит инициализацию j и i, они остаются в области видимости и код их может вполне себе использовать. Но они не инициализированны. В результате язык не допускает перепрыгивать инициализацию, если инициализированная переменная находится в области видимости в пункте, к которому переходит управление.

Эксперт CЭксперт С++

11126 / 6084 / 1663
Регистрация: 18.10.2014
Сообщений: 15,294

ЦитатаСообщение от Knjagskij Посмотреть сообщение

Почему, когда закомментирваны фигурные скобки не получается объявить переменную внутри switch?

Никаких проблем с объявлением как таковым в этом случае нет. Язык С++ запрещает "прыжки" в область видимости переменной, которые "перепрыгивают" инициализацию. Вот такой код компилироваться не будет

1 2 3
goto label; int i = 8; label:;

именно потому, что в объявлении переменной присутствует инициализатор. Именно по этой причине не компилируется и ваш switch : метка default: прыгает в область видимости переменной g , но перепрыгивает инициализацию этой переменной. Дополнительные фигурные скобки устраняют проблему.

Прыжки через неинициализированные объявления - разрешены. Если вы в вашем коде замените int g = 8; на раздельное int g; g = 8; , то он будет компилироваться в обоих вариантах (со всеми вытекающими).

2549 / 1208 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
TheCalligrapher, а есть выдержки с стандарта по вашим тезисам разрешены/не разрешены ?
Регистрация: 24.10.2015
Сообщений: 6
switch statement
http://en.cppreference.com/w/cpp/language/switch

Эксперт CЭксперт С++

11126 / 6084 / 1663
Регистрация: 18.10.2014
Сообщений: 15,294

ЦитатаСообщение от rikimaru2013 Посмотреть сообщение

а есть выдержки с стандарта по вашим тезисам разрешены/не разрешены ?

6.7 Declaration statement
.
3 It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A
program that jumps 91 from a point where a variable with automatic storage duration is not in scope to a
point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default
constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the
preceding types and is declared without an initializer.
.
91) The transfer from the condition of a switch statement to a case label is considered a jump in this respect.

P.S. Если кому-то будет интересно на досуге, я в свое время отвечал на аналогичный вопрос на SO, где также шла речь об определенных отличиях между С и С++. Язык С разрешает прыжки через инициализацию, однако в С аналог оригинального кода без скобок тоже не будет компилироваться, но совсем по другой, не связанной причине

87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь

Ругань на объявление массива внутри класса
Задание: создать класс-контейнер, реализующий политику FIFO. Имею код: CQueue.h #ifndef.

Пропуск условия if внутри switch
В общем я как-то подвис, и не могу взять в толк в чем проблема.В свитче по сути if должен же.

Внутри switch ошибка Case bypasses initialization of a local variable
Компилятор не устраивает case 3, там ввод массива автоматически , в чем ошибка подскажите Ошибку.

Switch case внутри switch case
Привет всем! Нужна помощь. Пишу калькулятор с консольным меню. Так вот, используется цикл.

Switch оператор . Для чего некоторые пишут break за пределами case?

Дело ли это вкуса или все же есть какие то примеры где именно такое использование break сыграло бы какую то иную роль?

Отслеживать
задан 1 ноя 2019 в 7:28
user252359 user252359

Возможно, скобки были вставлены для ограничения области видимости переменной, объявленной внутри. Тогда все что должно выполниться после ее деструктора - за скобками, включая break.

1 ноя 2019 в 7:43

@Chorkov Но если break внутри скобок, то он сам вызовет деструктор переменной. Так что разницы быть не должно.

1 ноя 2019 в 7:47
@HolyBlackCat разумеется, можно и внутри и снаружи. Речь об акцентировании внимания читающего.
1 ноя 2019 в 7:54

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

Нет никакой разницы.

Но вообще, вопрос странно поставлен.

Фигурные скобки - это не границы case . Их там может не быть вообще, или может быть несколько пар. Даже сам case может быть во вложенных фигурных скобках.

Еще, круглые скобки в case(true): не нужны.

Отслеживать
ответ дан 1 ноя 2019 в 7:37
HolyBlackCat HolyBlackCat
26.4k 3 3 золотых знака 27 27 серебряных знаков 39 39 бронзовых знаков
еще и для типа bool, принимающих всего два значения, нет смысла в конструкции switch
1 ноя 2019 в 7:45

У case нет никаких "пределов". Все "тело" switch - это один непрерывный statement, обычно (но не обязательно) составной, т.е. заключенный в <> . Метки case - это просто метки, т.е. точки входа в этот statement. По сути своей они ничем не отличаются от меток goto .

В "теле" switch , вперемешку с метками case , вы можете писать что угодно, в том числе вводить вложенные блоки <> , чьи границы никак не будут согласованы с расстановкой меток case . А можете не вводить никаких вложенных блоков вообще. В вашем примере совершенно не важно, где будет поставлен этот break - за пределами вложенного блока или внутри него.

Язык С++ лишь запрещает вам "прыгать" в область видимости автоматической переменной, пропуская ее объявление, если это объявление выполняет инициализацию. Это правило в одинаковой степени распространяется на переходы goto и на переходы switch/case .

Using int array for switch case

can anyone advise how to go about this. I am a bit stuck! 😀 I am trying at get the user to choose a number which is == to a case if the user wants to enter an amount under food he/she has to pick option 1 and then an amount. Hope this makes more sense that before! Thanks

Jalcock501
asked Mar 13, 2013 at 16:35
Jalcock501 Jalcock501
399 2 2 gold badges 6 6 silver badges 18 18 bronze badges
What are you trying to achieve with this? It is not clear.
Mar 13, 2013 at 16:36
Which value from the array are you expecting it to test in the switch?
Mar 13, 2013 at 16:36

4 Answers 4

You can't pass an array into a switch() you need to pass one of the elements in the array:

int item = 2; // or whatever switch(option_numbers[item])  

EDIT:
Based on your comment, if you want to take user input and have them pick one of the 4 options it's pretty simple, the array is totally unneeded.

int selection = -1; printf("Which option do you want? (1-4): "); scanf("%d", &selection); switch(selection)  
int option_numbers[3] = ; // that's not size 3, it's got 4 elements should // have been: // int option_numbers[4] = ; 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *