Странное поведение счетчика int в цикле for
- Войдите на сайт для отправки комментариев
Чт, 08/04/2021 - 13:26
Доброго всем дня.
В ардуино я новичок. А вот в программировании вроде нет.
И тут поведение одного скетча поставило меня в тупик.
Надо было слить дамп с EEPROM 256Кбит, соответственно адреса с 0 до 32767.
Для проверки сначала решил последние 700 ячеек вывести в Serial.
И увидел, что переменная типа int принимает значения больше 32767 и даже 65535 (unsigned int).
Вот такой примитивный код (редуцирован для нагядности)
void setup() { Serial.begin(115200); // Initialize the serial line Serial.println("START"); for (int i = 32000; i >= 0 && i <= 32767; i++) { Serial.println(i); } Serial.println("DONE"); } void loop() { }
Кто может объяснить, почему вывод в serial не заканчивается на 32767 ?
Я досмотрел до значений > 300 000
Вот такой примитивный код (редуцирован для нагядности)
приведенный вами код печатает строго от 32000 до 32767.
давайте показывайте, что вы там "редуцировали" для наглядности. Думаю. именно в той части собака и зарыта. Новички часто выкидывают из кода самые значимые части. Так что раз не понимаете. как код работает - значит и сокращать не умеете. Показывайте целиком.
Так, мысли в слух:
Если в условии цикла вместо "&&" поставить "||", то оно выполнялось бы всегда, но должно ж было бы сработать переполнение....
почему вывод в serial не заканчивается на 32767 ?
С какого перепугу ему заканчиваться? число типа int не может быть больше, чем 32767. Никогда и ни при каких обстоятельствах. У Вас стоит условие <=32767 - это условие истинно всегда, значит цикл бесконечный. С какого бодуна ему заканчиваться?
Если Вас интересует почему при этом не начинаю печататься отрицательные числа, посмотрите какие есть перегруженные методы print в классе Print - всё поймёте.
Если Вас интересует почему при этом не начинаю печататься отрицательные числа, посмотрите какие есть перегруженные методы print в классе Print - всё поймёте.
меня интересует, откуда у него начинают появляться числа "вплоть до 300 000"
приведенный вами код печатает строго от 32000 до 32767.
Да, ладно!
Вот мой код:
А вот его печать
меня интересует, откуда у него начинают появляться числа "вплоть до 300 000"
Оттуда же, откуда у меня в посте #5 появляются числа вплоть до 32777 (дальше я дожидаться не стал :-)
Спасибо за быстрый отклик. Код редуцирован от исходного, в котором были функции для опроса EEPROM и записи дампа на SD карту.
И вот именно приведенный код загружался в китайский клон Arduino Nano.
Запускаем монитор порта и видим.
START
32000
32001
...
32767
32768
32769
...
65535
65536
65537
Дождался значений больше миллиона.
у меня нет объяснений.
Сначала в цикле было одно условие i <= 32767. Потом я понял, что после достижения счетчиком значения 32767 и инкрементации счетчик станет равным -32768. Добавил второе условие i >= 0.
Однако после загрузки скетча в монитор порта выводятся бесконечно возрастающие положительные числа.
приведенный вами код печатает строго от 32000 до 32767.
Да, ладно!
Вот мой код:
а вот мой :)
и он печатает строго от 32000 до 32767 :)
(ладно-ладно, ошибку уже понял)
Осталься только вопрос. откуда ТС получил числа больше 65565 ?
Оттуда же, откуда у меня в посте #5 появляются числа вплоть до 32777 (дальше я дожидаться не стал :-)
ну на 0xFFFF разве не должно все закончится? - или я тоже чего-то не понимаю
Осталься только вопрос. откуда ТС получил числа больше 65565 ?
перегруженные методы print в классе Print - всё поймёте.
поэтому ?
У меня есть. Я всё описал в посте №5. Вы читаете, что Вам пишут или только "писатель"?
В таких случаях надо выкладывать получившийся код, т.к. нам неоткуда знать, что и как Вы добавили. Но в данном случае всё так и должно происходить, как Вы написали. См. последнюю фразу всё в том же посте №5
ну на 0xFFFF разве не должно все закончится? - или я тоже чего-то не понимаю
Вы просто давно (или никогда) не смотрели код класса Print. Метод для печати int выглядит вот так:
Спасибо разработчикам класса Print. Конечно же, это голимый ляп.
Согласен, int > 32767 никогда не будет. После инкрементации int равного 32767 значение должно стать -32 768
условие i >= 0 становится false - цикл завершается. По факту - фиг.
По поводу перегруженных методов print в классе Print. По факту счетчик int - два байта в ОЗУ, которые могут принять 65536 различных значения. Откуда в мониторе бесконечно возрастающие значения, больше 65536.
В первом посте приведен код, который грузился в Arduino Nano и который мы обсуждаем.
Там описание цикла выглядит вот так
for (int i = 32000; i >= 0 && i <= 32767; i++)
В первом посте приведен код, который грузился в Arduino Nano и который мы обсуждаем.
Там описание цикла выглядит вот так
for (int i = 32000; i >= 0 && i <= 32767; i++)
еще раз - у меня этот цикл пробегает от 32000 до 32767 один раз и останавливается.
проверяю в Линуксе, ардуины на работе нет. Переменная i имеет тип int16_t
Arduino под рукой нет? Залейте, пжлст, скетч из первого поста и проверьте. Я тоже понимаю, что так быть не должно, но ведь выходит :(
Понятно. В нормальных C/C++ этот код так и должен работать.
Вот у меня тоже работает таким странным образом.
Если заменить int на unsigned int код работает, как ожидается.
Мне в голову приходит только одно объяснение:
1. Оптимизатор удаляет проверки.
2. Метод print каким-то образом из int делает long, и код выполняется так, как если было long.
проверяю в Линуксе, ардуины на работе нет. Переменная i имеет тип int16_t
Да, в ардуине из коробки так, как он говорит. У тебя просто в опциях оптимизации другие.
Вы просто давно (или никогда) не смотрели код класса Print. Метод для печати int выглядит вот так:
Спасибо разработчикам класса Print. Конечно же, это голимый ляп.
рискну показаться тупым, но по-моему это ничего не обьясняет. Если параметр n в заголовке функции описан как int -как он может передать в тело функции значение, большее чем 0xFFFF ???
smailik,
Вы задаёте много РАЗНЫХ вопросов оптом и не внимательно читаете ответы.
Давайте схаваем этого слона по частям.
1. Почему не заканчивается при условии i <= 32767 я Вам объяснил. Надеюсь. Вы поняли.
2. Условия типа i > 0 выбрасывает оптимизатор, т.к. он видит, что число и так больше 0 и только увеличивается (ну, вот такой он)
3. Печатается в методе Print::print ВСЕГДА long. Если Вы поставите более аккуратную печать, например
будут отрицательные числа и море счастья.
Все вопросы закрыты?
Если параметр n в заголовке функции описан как int -как он может передать в тело функции значение, большее чем 0xFFFF ???
Посмотрите ассемблерный код.
Да, перегруженная функция действительно выводит int как long (спасибо за объяснение ЕвгенийП).
Но это не объясняет того, как сам int может стать больше 0xFFFF.
И это странный оптимизатор, который из двух условий, объединенных логическим И - одно отбрасывает.
Посмотрите ассемблерный код.
негде :(
подозреваю. что это очередная "оптимизация" и все вызовы метода для печати инт еще на этапе компиляции подменяется на печать лонг. Это (для меня) обьяснило бы остальное. но как-то очень криво это все...
smailik,
запомните. Возле переполнения всегда случаются странные вещи. Причём у разных компиляторов/оптимизаторов - разные. Если Вам нужно работать возле переполнения, то лучший выход - взять тип данных подлиннее. В данном случае отца русской демократии спасёт unsigned. Если надо работать возле переполнения unsigned - берите long. Трахаться с переполнением есть смысл только тогда, когда длиннее переменных уже нет.
Не ходите по ночам на болота.
Да, перегруженная функция действительно выводит int как long (спасибо за объяснение ЕвгенийП).
Но это не объясняет того, как сам int может стать больше 0xFFFF.
И это странный оптимизатор, который из двух условий, объединенных логическим И - одно отбрасывает.
Вам тоже совет из поста #22
Еще раз спасибо. Насчет оптимизатора - звучит правдоподобно. Хотя очень криво. А вдруг я со счетчиком что-то еще в цикле делаю, например уменьшаю. Теперь при составлении сложных условий надо еще соображать, какие из них оптимизатор решит скипнуть.
По поводу метода print. int - это два байта в ОЗУ, значения от 0 до 0хFFFF. Как бы он не выводил тип int - на входе все равно эти два байта. Как может получаться больше 0xFFFF? Согласен с b707, это крайне странно.
Тем не менее, большое спасибо за активное участие ЕвгенийП и b707.
Мир не идеален :) Примем эти странности как особенность и будем осторожны на границе overflow.
Посмотрите ассемблерный код.
негде :(
подозреваю. что это очередная "оптимизация" и все вызовы метода для печати инт еще на этапе компиляции подменяется на печать лонг. Это (для меня) обьяснило бы остальное. но как-то очень криво это все...
Так оно и есть:
1. Оптимизатор выбрасывает все сравнения, единственной операцией с i стается только i++.
2. Операцию i++ оптимизатор переносит внутрь print.
3. А внутри print переменная i уже имеет тип long, что мы и видим на печати.
Можно проанализировать листинги:
В варианте с int присутствует следующий фрагмент, который отсутствует в варианте с unsigned int
а в варианте с unsigned int, наоборот, присутствует фрагмент, отсутствующий в варианте с int:
Насчет изучения ассемблерного кода.
Я использую Arduino для быстрого решения мелких прикладных задач. И ожидаю от несложного кода на несложном языке предсказуемого поведения. Тратить время на изучение конкретной имплементации методов просто нет.
smailik,
запомните. Возле переполнения всегда случаются странные вещи. Причём у разных компиляторов/оптимизаторов - разные. Если Вам нужно работать возле переполнения, то лучший выход - взять тип данных подлиннее. В данном случае отца русской демократии спасёт unsigned. Если надо работать возле переполнения unsigned - берите long. Трахаться с переполнением есть смысл только тогда, когда длиннее переменных уже нет.
Не ходите по ночам на болота.
Ну да, достаточно вспомнить тему про "святое переполнение миллис". И утверждение, что при правильной обработке переполнения никаких проблем никогда не бывает.
А теперь обращаем внимание на выделенные слова и думаем, которому из ЕвгениевП верить: который в теме про миллис, или который в теме про int в цикле for.
4ЕвгенийП: ничего личного.
andriano, спасибо Вам за такой подробный обзор. Теперь вопрос точно закрыт.
Но работа оптимизатора повергает в шок. Особенно вот это
2. Операцию i++ оптимизатор переносит внутрь print.
Топик можно закрывать.
Всем спасибо.
Насчет изучения ассемблерного кода.
Я использую Arduino для быстрого решения мелких прикладных задач. И ожидаю от несложного кода на несложном языке предсказуемого поведения. Тратить время на изучение конкретной имплементации методов просто нет.
Ну тогда пишите сразу правильно. И никогда не пишите int i ... i <= 32767, и будет Вам счастье.
Другими словами, глюки компилятора проявляются исключительно на неправильном коде.
И еще, сюда же. Если Вы хотите писать несложный код с предсказуемым поведением, то, боюсь, Вы выбрали не самый подходящий язык. Паскаль бы с этим справился гораздо лучше. А Си предполагает, что программист знает, что делает, и отчетливо осознает, почему делает именно так.
Паскаль бы с этим справился гораздо лучше. А Си предполагает, что программист знает, что делает, и отчетливо осознает, почему делает именно так.
Ой! В 1984-ом году я учился в матшколе и было мне 15 лет. Вместо быдляцкого УПК, у нас было программирование, которое вели ребята из ИПМ. Так вот я сломал БЭСМ-6 у них в ВЦ программой НА ПАСКАЛЕ ;))))))). Причем я её на синем бланке отдавал в перфорацию, то есть её много раз проверяли ;)). Но то, что я во вложенных циклах файлы открываю, никто не заметил... Защиты от большого числа открытых файлов в системе не было :(.
Я - не нарочно, не ожидал таких последствий ;)). Хотел сделать просто мелкую пакость. ИПМ-шики, как ни странно не обиделись, а зауважали. Хорошие были времена!
Ой! В 1984-ом году...
Влад, я думаю, что ты завалил не компилятор, а ОС. Так что Паскаль здесь ни при чем. Он не обязан знать ограничения ОС.
И еще, сюда же. Если Вы хотите писать несложный код с предсказуемым поведением, то, боюсь, Вы выбрали не самый подходящий язык. Паскаль бы с этим справился гораздо лучше. А Си предполагает, что программист знает, что делает, и отчетливо осознает, почему делает именно так.
Andriano, под несложным кодом я подразумевал легко читаемый код, в основном состоящий из функций, простых классов, циклов, операторов условного ветвления т.е. процедурное программирование + немножко ООП. Без лямбда-функций, перегрузок и прочих премудростей.
Я в первую очередь использую Arduino для быстрого решения несложных задач по автоматизации, а в Arduino IDE Си по дефолту и речь о выборе языка программирования не особо стоит.
А на Паскале последний раз кодил в 90-х, создавали ПО для различных количественных и качественных оценок гистологических срезов (медицина). Легкий оффтоп.
всегда случаются странные вещи.
проблем никогда не бывает.
которому из ЕвгениевП верить
Обоим. "странные вещи" != "проблемы".
Я в первую очередь использую Arduino для быстрого решения несложных задач по автоматизации, а в Arduino IDE Си по дефолту и речь о выборе языка программирования не особо стоит.
это все хорошо, только упаси вас бог скатываться в ересь "у меня слишком мало времени чтобы углубляться в нюансы".
Обычно так говорят убогие лентяи, которые реально и не думали никуда углублятся. Этот путь не приведет ни к чему хорошему - как в программировании, так и особенно - в общении тут на форуме :)
это все хорошо, только упаси вас бог скатываться в ересь "у меня слишком мало времени чтобы углубляться в нюансы".
Обычно так говорят убогие лентяи, которые реально и не думали никуда углублятся. Этот путь не приведет ни к чему хорошему - как в программировании, так и особенно - в общении тут на форуме :)
Та ни в жисть :) Просто рациональное использование самого драгоценного ресурса.
За совет спасибо, особенно насчет форума. Уже получил определенное представление :)
всегда случаются странные вещи.
проблем никогда не бывает.
которому из ЕвгениевП верить
Обоим. "странные вещи" != "проблемы".
Приношу свои извинения: отсутствие проблем, действительно, выглядит очень странным, если не сказать подозрительным.
Я в первую очередь использую Arduino для быстрого решения несложных задач по автоматизации, а в Arduino IDE Си по дефолту и речь о выборе языка программирования не особо стоит.
Нет, ну вопрос о том, почему авторы Ардуино в качестве основного языка выбрали Си/Си++, думаю, выходит за рамки настоящей темы. Я просто хотел сказать, что, коль Вы выбрали систему (Ардуино), в которой используются Си/Си++, то "Тратить время на изучение" придется, хотите Вы этого или нет.
Без лямбда-функций, перегрузок и прочих премудростей.
Ну это ж колебание основ и противу всяких скреп! Щаз Пуха натравим на еретика! ;)))
Как ж без лямбд? Решительно не понимаю!
Влад, я думаю, что ты завалил не компилятор, а ОС. Так что Паскаль здесь ни при чем. Он не обязан знать ограничения ОС.
Тут и думать не нужно. Как можно компилятор, "завалить"? Ты ж сказал, что на Паскале новичок не сделает много ошибок (ну я так тебя понял) - я показал, как именно можно сделать большие ошибки. Фсё. ;))
Это просто трёп, Сереж! Не диспут.
Ты ж сказал, что на Паскале новичок не сделает много ошибок (ну я так тебя понял)
Нет, я имел в виду, что компилятор Паскаля сообщит новичку о гораздо большем проценте имеющихся ошибок, тогда как компилятор Си будет считать написанное не за ошибку, а что программист именно "хотел чего-то странного". Поэтому написанная новичком программа, которую пропустит компилятор, на Си будет иметь гораздо большую вероятность "работать как-то странно", чем на Паскале.
Короче: Си не прощает ошибок, хотя внешне выглядит все наоборот.
Попробую резюмировать весь топик.
Причины "странного" исполнения кода были выявлены в кратчайшие сроки.
Их оказалось три:
1. "Неправильное" написание мною кода.
2. Как следствие пункта 1 - кривая работа оптимизатора, выкинувшего все условия проверки значения счетчика.
3. Кривая реализация метода Print для int.
Насчет пункта 1 - попробую слабенько защититься. Код, конечно, "скользкий". Но вот у b707 под Линуксом этот же код скомпилился нормально и отработал ожидаемо. Ладно, согласен, отмаза так себе.
В процессе обсуждения получил массу советов.
1. Учиться.
2. Не лениться.
3. Выбрать для себя ЯП попроще, соответственно уровня. Но так как для Arduino альтернативы Си практически нет - то
3.1 - Попросить у Гудвина мозгов
3.2 - см. п. 1 и 2.
4. Быть внимательным и готовым к неожиданному поведению кода при работе рядом с границами предельно допустимых значений типа.
Стараться избегать таких ситуаций, используя более длинные типы.
Узнал, что Си - это для реальных пацанов, он, как мамка, за тобой следить не будет.
Максимально толерантен к странному стилю программирования.
- Хотите, чтобы я откомпилировал этот код? ОКей, счас сделаем, но потом, как сказал Сусанин полякам - "Дальше сами!".
Ну и чтобы совсем не скучно было, я его еще пооптимизирую немного :)
Что имеем в сухом остатке.
Причины проблемы выявлены, предложены пути ее решения, за что я искренне благодарен всем участникам обсуждения.
Что имеем в сухом остатке.
Причины проблемы выявлены, предложены пути ее решения, за что я искренне благодарен всем участникам обсуждения.
Ты просто warning-и включи и читай, как тебя за глаза материт компилятор
Ты просто warning-и включи и читай, как тебя за глаза материт компилятор
Включил варнинги (-Wall -Wextra) - при компиляции тишина. (Или не так включил?)
Отключил оптимизацию всю (Os -> O0) - скетч отработал, как положено, но размер скомпилированного кода вырос в 5 раз :( Нафик-нафик. Вернул оптимизацию взад.
Содержимое platfroms.txt и скриншот окна "Настройки" Arduino IDE в студию.
Содержимое platfroms.txt и скриншот окна "Настройки" Arduino IDE в студию.
Скетч все тот же самый.
В настройках
Показать подробный вывод - Компиляция - стоит галка
Сообщения компилятора - Все
Вот начало platform.txt, остальная часть без изменений
smailik, вот я реально хочу запустить, посмотреть что там и, если получится, чем-то Вам помочь. Ну, не пишите Вы мне
Вообще, создавать дополнительные трудности тому, кто хочет Вам помочь - так себе идея.