Cтранное поведение swith
- Войдите на сайт для отправки комментариев
Пт, 03/07/2020 - 12:21
Здравствуйте! Столкнулся со странным поведением swith. Подскажите, пожалуйста, где ошибка. Вот этот блок кода работает неправильно.
switch (stste_menu) {// Показываем, нам не жалко!
case 0:
Serial.println ("Текущее время ХХ:ХХ");
//displayClock(uint8_t hrs, uint8_t mins);
disp.point (1);
break;
case 1:
Serial.print ("Обороты в минуту: ");
Serial.println (rpm);
if (rpm >= 1000) {
DispData [0] = _H;
DispData [1] = _H;
DispData [2] = _H;
DispData [3] = _P;
} else {
DataNew = rpm;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _P;
}
disp.displayByte(DispData);
disp.point (0);
break;
case 2:
Serial.print ("Скорость: ");
Serial.println (km_h);
if (km_h < 100) {
DataNew = km_h * 10;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _U;
} else {
DispData [0] = _H;
DispData [1] = _H;
DispData [2] = _H;
DispData [3] = _U;
}
disp.displayByte(DispData);
disp.point (1);
break;
case 3:
Serial.print ("Температура: ");
Serial.println (temp);
if (temp < 100) {
DataNew = temp * 10;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = 0x63;
} else {
DispData [0] = _H;
DispData [1] = _H;
DispData [2] = _H;
DispData [3] = 0x63;
}
disp.displayByte(DispData);
disp.point (1);
break;
case 4:
Serial.print ("Дистанция за поездку: ");
Serial.println (dist_metr);
if (dist_metr < 1000) {
DataNew = dist_metr;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = 0x52;
disp.displayByte(DispData);
disp.point (0);
} else if (dist_metr >= 1000 && dist_metr < 100000) {
DataNew = dist_metr / 100;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = 0x52;
disp.displayByte(DispData);
disp.point (1);
}
break;
case 5:
Serial.print ("Время поездки: ");
Serial.println (time_trip_min);
disp.displayClock(time_trip_min / 60, int(time_trip_min) % 60);
if (millis () - timer_clock_trip >= 1000) {
timer_clock_trip = millis ();
state_point = !state_point;
disp.point (state_point);
}
break;
case 6:
Serial.print ("Всего километров: ");
Serial.println (mileage_km);
if (mileage_km < 1000) {
DataNew = mileage_km;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _S;
disp.displayByte(DispData);
disp.point (0);
} else if (mileage_km >= 1000) {
DataNew = mileage_km / 100;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _S;
disp.displayByte(DispData);
disp.point (1);
}
break;
case 7:
Serial.print ("Всего часов: ");
Serial.println (mileage_time_hour);
if (mileage_time_hour < 1000) {
DataNew = mileage_time_hour;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _H;
disp.displayByte(DispData);
disp.point (0);
} else if (mileage_time_hour >= 1000) {
DataNew = mileage_time_hour / 100;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _H;
disp.displayByte(DispData);
disp.point (1);
}
break;
case 8:
Serial.print ("Проценты батарейки: ");
Serial.println (percent_batt);
int NewPercent = percent_batt;
if (percent_batt < 100 && percent_batt >= 10) {
NewPercent = (NewPercent * 100) + 8;
} else if (percent_batt < 10) {
NewPercent = (NewPercent * 1000) + 8;
} else if (percent_batt == 100) {
NewPercent = (NewPercent * 10) + 8;
}
disp.displayInt (NewPercent);
disp.point (0);
break;
case 9:
Serial.print ("Средняя скорость:");
if (time_trip_min == 0) {
Serial.println (0);
disp.displayByte (_empty, _empty, _0, _A);
disp.point (0);
} else {
Serial.println (((dist_metr / time_trip_min) * 0.06));
if (((dist_metr / time_trip_min) * 0.06) < 100) {
DataNew = ((dist_metr / time_trip_min) * 0.06) * 10;
Data = DataNew / 100;
DispData [0] = int_to_byte (Data);
DataNew -= Data * 100;
Data = DataNew / 10;
DispData [1] = int_to_byte (Data);
DataNew -= Data * 10;
Data = DataNew / 1;
DispData [2] = int_to_byte (Data);
DispData [3] = _A;
} else {
DispData [0] = _H;
DispData [1] = _H;
DispData [2] = _H;
DispData [3] = _A;
}
disp.displayByte (DispData);
disp.point (0);
}
break;
}
Когда переменная stste_menu становится равной 9, в порт ничего не выводится. Но. Когда я объявляю переменную NewPercent не после
case 8:
а глобально, все работает хорошо. В чем может быть проблема?
Извините, пожалуйста за неправильное название switch в названии, опечатался
и канпилятор тебе никаких warning-ов не пишет?
Когда переменная stste_menu становится равной 9, в порт ничего не выводится.
А она точно становится равной 9? В приведенном коде этого не видно
Комбинация условий строк 193 - 199 -полный бред, но влияет ли это на вывод case 9 - сказать сложно
короче, после case 8: поставь {
а закрывающую } - перед break этого блока.
Работает! А почему?
аптамуш, что внутри switch локальные (для одной ветки case) переменные могут быть только внутри блока {}.
я ж не зря тебя спрашивал, а не ругается ли канпилятор, например на этот код
switch (Msg.Message) { case msg_Error: int ErrorCode = Msg.LoParam; Serial << ErrorCode << eoln; break; default: break; }примерно такими словами:
ну так у тебя, наерна, все ворнинги отключены же, как у страуса.
Чьордвозмьи, надо бы записать )))
Спасибо!
В принципе, можно и без скобок, но нельзя в одной строке и объявить, и инициализировать
switch (x) { case 1: int z; // ок, объявление разрешено z = 5; // ок, операция присваивания разрешена break; case 2: z = 6; // ок, переменная z была объявлена выше, поэтому мы можем использовать её здесь break; case 3: int c = 4; // нельзя, вы не можете инициализировать переменные внутри case break; }Поскольку полной программы нет, то предполагаю, что stste_menu не становится 9-ти.
короче, после case 8: поставь {
а закрывающую } - перед break этого блока.
Не - в свитче он выполняет последовательно до сброса. Скобки не важны.
Скобки важны при объявлении переменных внутри switch
При инициализации.
Что то "ходячая энциклопедия" ЕвгенийП молчит...) Почему бы не объяснить людЯм? А то я смотрю никто так и не понял почему объявлять можно, а инициализировать переменную после кейса нельзя.
Без фигурных скобок имеется ввиду.
Почему бы не объяснить людЯм?
В отличие от Вас, нет у меня "излагательного" таланта, могу только на пальцах.)
// Мы не можем инициализировать локальную переменную внутри кейса, т.к. инициализация // переменной требует выполнения, а case 1 может никогда не выполниться switch (x) { case 1: int a = 1; //инициализируем - ошибка, даже если переменная а break; // не будет использоваться в других кейсах case 2: break; } // Как говорили ранее, блок внутри кейса, позволяет инициализировать переменную. // Но вне блока (в других кейсах) она, само собой, будет не видна switch (x) { case 1: { int a = 1; //OK //... break; } case 2: a = 2; //ошибка break; } // Здесь всё красиво. Особенность в том, что хотя переменная определяется внутри кейса, но // она не создаётся в этом месте, а помещается в начало блока, т.е, сразу после switch { switch (x) { case 1: break; case 2: int a; //определяем a = 2; break; case 3: a = 3; //работаем и в других кейсах break; }Я столкнулся с этим пару лет назад, потому и запомнил.)
Сразу после switch получится впихнуть int a=1?
Зелёный, ты чучуть ошибаешься, при инициализации переменной в case ошибки нет, даже ворнинга нет, есть просто note от канпилятора, просто тогда почему-то блоки из нижних case никада не выполняются. Внимательно посмотри выдачу канпилятора в #8.
Странно тогда. Неуправляемый компилятор какой-то - сам декларации переносит куда удобно, а пользователю не даёт
Или я что-то не так понял? Што там после мясорубки ассемблеровской за фарш получается?
Зелёный, ты чучуть ошибаешься, при инициализации переменной в case ошибки нет, даже ворнинга нет, есть просто note от канпилятора, просто тогда почему-то блоки из нижних case никада не выполняются. Внимательно посмотри выдачу канпилятора в #8.
Проверил.
void setup() { int x = 0; switch (x) { case 1: int a = 1; //инициализируем - ошибка, даже если она break; // не будет использоваться в других кейсах case 2: break; } } void loop() { }Может от версии IDE (компилятора) зависит?
В отличие от Вас, нет у меня "излагательного" таланта, могу только на пальцах.)
Дело в не в излагательном таланте. У Вас (судя по Вашим примерам и выводам из них) нет понимания логики вопроса, потому Вы выхватываете отдельные факты и не видите леса за деревьями. И кстати,
Давайте посмотрим:
// Мы не можем инициализировать локальную переменную внутри кейса, т.к. инициализация // переменной требует выполнения, а case 1 может никогда не выполниться switch (x) { case 1: int a = 1; //инициализируем - ошибка, даже если переменная аМожем. Компилятор предупредит, что у нас что-то не так, но честно всё скомпилирует.
// Здесь всё красиво. Особенность в том, что хотя переменная определяется внутри кейса, но // она не создаётся в этом месте, а помещается в начало блока, т.е, сразу после switch { switch (x) { case 1: break; case 2: int a; //определяем a = 2; break; case 3: a = 3; //работаем и в других кейсах break; }<
Но, тут прямая ошибка - он создаётся именно в этом месте. Это очень легко проверить. Возьмите не int, а что-нибудь, имеющее конструктор. Поставьте печать перед объявлением, в конструкторе и после объявления - увидите, что создаётся точно в том месте, где объявлено. Вот, пример:
struct Kaka { Kaka(void) { Serial.println("Constructor"); } }; void setup(void) { Serial.begin(57600); Serial.print("Go on!\r\n"); int n = digitalRead(3); switch (n) { case 2: Serial.println(2); break; case 0: Serial.println("Before"); Kaka kaka; Serial.println("After"); break; case 3: Serial.println(3); break; } } void loop(void) {}Результат
Я столкнулся с этим пару лет назад, потому и запомнил.)
Зря тогда же не разобрались. Там всё просто и, если понимать суть дела, то ничего загадочного.
Я тоже не разбирался. :-) Не работает, значит на то есть у него своя сакральная правда. :-) Няхай.
А какая переменная 'а' будет использоваться в строке #17 (case 3, a=3), если декларация не будет выполнена?
Всё равно что такая:
switch (x) { int a; //определяем case 1: break; case 2: a = 2; break; case 3: a = 3; //работаем и в других кейсах break; }Насчёт ошибок.
#define F_CPU 16000000UL int main() { int x = 1; switch (x) { case 1: int a = 1; break; case 2: a = 2; break; } }Всё равно что такая:
switch (x) { int a; //определяем case 1: break; case 2: a = 2; break; case 3: a = 3; //работаем и в других кейсах break; }Однако у ЕвгенияП инит происходит в том же кейсе, где и объявление произведено. Не складывается у меня картинка
Декларация не может быть выполнена или не выполнена - она неисполняема. Не выполненной может быть инициализация. Объявление же оно как скрипач - всегда в законе. Например, вот вполне корректная запись:
switch (n) { int a; // это место ВООБЩЕ никогда не исполняется. Объявлению это не мешает case 2: a = 2; // это та самая a из строки №2 Serial.println(a); break; case 0: a = 3; // и это та самая a из строки №2 Serial.println(a); break; }Насчёт ошибок.
Либо там выбрана опция "трактовать предупреждения, как ошибки", либо это самодеятельность реализации. Язык такое вполне позволяет и, как видите, GCC (который с IDE) вполне это ест. Предупреждает о странности, но ест.
Просто я синтаксически ожидал, что компилятор, перепрыгнув на кейс 3, проигнорирует объявление переменной. А он декларации без инициализации выносит в начало блока {}, выходит.
Кстати, а чего Вы собственно к свичу привязались? Ситуация-то общая для любого "обхода инициализации", свич тут не при делах, он только частный случай. Вот, например, то же самое (и по сути, и по сообщениям) безо всякого свича.
void setup(void) { Serial.begin(115200); if (digitalRead(3)) goto kaka; int n = digitalRead(5); kaka: Serial.println(n); }Тема про свич, слово за слово, как говорится...
Понятно, что для одного его такую петрушку не стали бы разработчики сажать - механизьм универсальный.
Ничего он никуда не выносит.
Ну, можно говорить, что выносит, но тогда уж всё - хоть с инициализацией, хоть без.
В общем, существует очень расхожее мнение о том, что локальные переменные видимы "с момента объявления и до конца блока" (например). Это в принципе неверно. Это упрощение для чайников (для первоначального объяснения). Но когда нечайник пытается понять какую-то языковую тонкость, то упрощение ему мешает! Вот тот самый случай.
Область видимости переменной - весь блок. Но пользоваться ею можно только после объявления (здесь "после" надо буквально понимать как "ниже по тексту"). И инициализация её происходит в месте объявления!
Т.е. смотрите, программ входит в блок. Если у нас нет изощрённой оптимизации, то в момент входа в блок на стеке (или где там ещё) резервируется место под все переменные, объявленные внутри блока (в любом его месте). Но пользоваться переменой можно только начиная с точки объявления.
Почему так сложно - это пережиток тех времён, когда переменные в Си можно было объявить только в начале блока. С тех пор разрешили объявлять где попало, но делать выделение памяти на стеке где попало - очень сложно и часто неоднозначно. Потому сделано так, как сделано.
Свич же - самый обыкновенный блок (один) с метками и механизмом перехода на эти метки. Но это именно блок и метки (в стандарте они так и называются "Case labels" и это просто разновидность метки). Более того, они даже не обязаны находиться на одном блочном уровне. Метки же не обязаны! Вот смотрите, на пример, где разные кейсы находятся на разных уровнях иерархии блоков. И это не мешает им нормально работать:
void setup(void) { Serial.begin(57600); int n = digitalRead(3); switch (n) { case 0: { Serial.println("ZERO"); break; case 1: Serial.println("ONE"); break; } } } void loop(void) {}а вот ещё большее извращенство, но тоже нормально работает
void setup(void) { Serial.begin(57600); int n = digitalRead(3); switch (n) { case 0: { Serial.println("ZERO"); { break; case 1: Serial.println("ONE"); break; } } } } void loop(void) {}дайте HIGH на третий пин и он радостно скажет ONE
Область видимости переменной - весь блок. Но пользоваться ею можно только после объявления (здесь "после" надо буквально понимать как "ниже по тексту"). И инициализация её происходит в месте объявления!
Хочу уточнить про буквальность: т.е. в кейсе, что находится выше объявления, обращение к переменной будет ошибочно, а в кейсе пониже - уже валидным? Т.е. как понимать слово "можно" - какие последствия будут при обращении до объявления? К сожалению на телефоне канпилятора нет - проверить нет возможности, а сам я такими штуками не балуюсь - стараюсь, чтобы все было или квадратно или покрашено в зелёный цвет.
Если это так, то тут, полагаю, появляется интересная деоптимизирующая способность такого извращения. Я читал, что оптимизатор хитрым образом строит таблицу переходов и джамп на кейс 3 вполне в может быть ближе джампа на кейс 1. Так что, ежели требуется буквальная трактовка последовательности кейсов, то оптимизатор бреется. Или я уже ударился в фантазии?
del
Ещё раз. Фигурные скобки после switch - это самый обыкновенные блок. Выражения case - просто метки и не более. Никакого влияния на области видимости переменных они (кейсы) не оказывают от слова совсем. Просто блок, а внутри метки. Вот это нужно понимать чётко.
А теперь, понимая, что это самый обыкновенный блок, кейсы блоками не являются, а являются просто метками, сами ответьте на свой вопрос. Что будет если в любом блоке объявить переменную после использования? Или начать использовать до объявления? Будет сообщение про необъявленную переменную и ничего более.
Я ответил?
Я читал, что оптимизатор хитрым образом строит таблицу переходов и джамп на кейс 3 вполне в может быть ближе джампа на кейс 1. Так что, ежели требуется буквальная трактовка последовательности кейсов, то оптимизатор бреется.
Не очень понял о чём это, но прошу заметить, что если не будет брейков и сработает первый из кейсов, то все остальные кейсы будут выполняться строго в той последовательности, в которой они написаны - сверху-вниз и никакой оптимизатор этого не изменит.
Насчёт ошибок.
Либо там выбрана опция "трактовать предупреждения, как ошибки", либо это самодеятельность реализации. Язык такое вполне позволяет и, как видите, GCC (который с IDE) вполне это ест. Предупреждает о странности, но ест.
Что значит "это самодеятельность реализации"? Командная строка перед Вами, никаких опций.
Но, тут прямая ошибка - он создаётся именно в этом месте. Это очень легко проверить. Возьмите не int, а что-нибудь, имеющее конструктор. Поставьте печать перед объявлением, в конструкторе и после объявления - увидите, что создаётся точно в том месте, где объявлено.
С конструктором не убедили. Думаю, это не чистый эксперимент.)
del
Что значит "это самодеятельность реализации"?
Так называют ситуацию, когда авторы реализации отходят от стандарта языка (расширяют язык или сужают его). Типичный пример - диапазонные кейсы в GCC - в языке такого нет.
Командная строка перед Вами, никаких опций.
И что, я не знаю какие там опции по умолчанию. У меня в IDE Ваш пример нормально скомпилировался. С предупреждениями, но без ошибок. И с точки зрения языка это правильно.
С конструктором не убедили. Думаю, это не чистый эксперимент.)
Читайте стандарт языка, может тогда убедитесь :-)
sadman41,
на самом деле, там всё ещё смешнее. Как я уже сказал, главное понимать, что фигурные скобки после switch - это самый обыкновенные блок в начале которого есть пачка if'ов c goto. Выражения case - просто метки и не более. Если это понимать, то можно смело предполагать как именно он сработает в той или иной ситуации. ну, вот, например, посмотрите на эту шикарную конструкцию (задача сформулирована в комментариях) - и работает ведь!
// // ОСТОРОЖНО - ГОВНОКОД !!! // Начинающим не смотреть вовсе! // все трюки выполнены профессионалами - не пытайтесь повторить самостоятельно // void setup(void) { Serial.begin(57600); } void loop(void) { int n = random(0, 3); Serial.print("n="); Serial.println(n); // // ЗАДАЧА: // Если n==1 - вывести "A" // Если n==2 - вывести "B" // Если n==0 И на пине №3 HIGH - вывести "A" // Если n==0 И на пине №3 LOW - вывести "B" // switch (n) { case 0: if (digitalRead(3)) { case 1: Serial.println("A"); } else { case 2: Serial.println("B"); } } delay(500); }Надеюсь, мисрасты, структурасты и прочие сектанты этого не увидят, иначе быть мне съеденным под соусом из дерьма. Но, как видите, оператор switch - это просто кладезь возможностей для истинно-творческого человека :-)))
P.S. если кто-то хочет сказать мне, что за переходы внутрь блоков if и else, минуя собственно проверку условия, мне предстоит гореть в аду - станьте в очередь, ибо Вы не первый. А что до ада, так там будет отличная компания - Кнут, Брукс и др., а в раю что ... с Виртом что-ли тёрки тереть?
Так называют ситуацию, когда авторы реализации отходят от стандарта языка (расширяют язык или сужают его). Типичный пример - диапазонные кейсы в GCC - в языке такого нет.
И что, я не знаю какие там опции по умолчанию. У меня в IDE Ваш пример нормально скомпилировался. С предупреждениями, но без ошибок. И с точки зрения языка это правильно.
Тут скорее НАШ IDE отходит от стандарта. Возьмём GCC4.9.2 для старшего брата:
int main() { int x = 0; switch (x) { case 1: int a = 1; break; case 2: break; } }Green,
я уже второй день Вам говорю, что в стандарте НЕТ запрета на обход инициализации при помощи перехода на метку операторами goto или switch. Да, это идиотизм, но это незапрещённый идиотизм. Вот, например, запрет входить внутрь блока catch при помощи goto/switch есть - да, это запрещено. А обходить инициализацию - нет такого запрета.
Поэтому, если Вы заявляете, что
Тут скорее НАШ IDE отходит от стандарта.
то уж будьте последовательны - и укажите какой именно параграф стандарта запрещает такие переходы и (стало быть) нарушается компилятором GCC, который поставляется с IDE.
Утверждаете, что такой запрет есть - покажите.
Нет, я не знаю стандартов и у меня нет желания с ними разбираться. Но я вижу что более старые версии IDE (другие версии GCC) дают ошибку ввиду невозможности инициализации локальной переменной в блоке switch без задания дополнительного блока, в отличие от свежих версий IDE (у меня 1.8.12), которые ограничиваются предупреждением. При этом Вы считаете что все эти другие версии неправильные, что весьма странно, не находите?
Касательно наших с Вами расхождений, как я понимаю, они в сравнении блока switch с обычным блоком. Вы утверждаете что разницы нет, тогда как разница очевидна. И это видно на примерах. И я тоже уже второй день именно об этом Вам говорю.
Давайте Вы попробуете чётко и без шелухи ответить именно на этот вопрос. В чём же всё-таки разница между switch и любым другим блоком? Ну, не считая синтаксического сахара типа того, что пачку проверок и goto компилятор сам вставит, а другом блоке пришлось бы руками писать.
В который раз?
Я в упор не вижу (не хочу видеть) никаких проверок и никаких goto.
Разница в том что мы не можем инициализировать локальную переменную.
В одной версии компилятора получаем ошибку, в другой предупреждение.
Это нужно рисовать или и так понятно?
Грин, для меня всё ясно стало в #34. А ты пошто споришь?