Объявление переменной внутри основного цикла
Раньше я объявлял переменные внутри основного цикла, и это работало нормально. В новом проекте я сделал то же самое:
void loop(void)
{
uint8_t counter;
....
if (buttonPress)
counter = 0;
...
if (someCondition == true)
{
Serial.println(counter);
counter++
}
}
Но затем вывод переменной counter показывает, что по какой-то причине переменная counter сбрасывается на 0, а не идет вверх. Это происходит даже после отпускания кнопки.
Областью действия переменной counter является основной цикл, и я просто предположил, что она сохранит свое значение и подсчитает.
По какой причине эта переменная может быть сброшена до 0? Это другие функции вызываются внутри основного цикла? Время от времени вызывается обработчик прерывания?
Решение, которое я нашел, состоит в том, чтобы объявить переменную статической:
static uint8_t counter;
Затем он увеличивается, как и ожидалось.
Несмотря на то, что я нашел решение, мне очень хотелось бы понять, в чем заключалась проблема с моим первым подходом.
@Sören, 👍3
Обсуждение3 ответа
Проблема в том, что вы объявляете новую переменную. Таким образом, несмотря на то, что имя такое же, локальная переменная уничтожается в конце цикла и создается заново в начале.
На самом деле хорошего решения нет:
- Глобальные переменные лучше не использовать. Их можно рассматривать как «одиночки», например переменные, которые нужны во многих местах кода и встречаются только один раз.
- Как вы выяснили, статика решает проблему. Я бы использовал этот вариант, хотя лично я не сторонник статических переменных.
Еще один способ — поместить цикл через некоторое время, чтобы получить:
пустой цикл(пустой) { счетчик uint8_t = 0; // Всегда инициализируйте переменные пока (правда) { // Ваш код }
Это тоже сработает, так как ваш счетчик инициализируется только один раз, однако цикл уже (внутри) является циклом while, поэтому у вас их два.
Намного лучший способ, но требующий дополнительной работы, — создать класс C++, где counter может быть переменной класса, которая является локальной для этого класса (которая все еще может быть синглтоном). Конечно, это верно только в том случае, если переменная-счетчик логически принадлежит этому классу, если это не так, просто оставьте ее глобальной переменной (обновленной после замечания Эдгара Боне).
Сделать счетчик свойством объекта, который сам является глобальной переменной, было бы не лучше, чем сделать глобальным сам счетчик. На самом деле было бы еще хуже: любая сложность, которая не служит никакой полезной цели, — это всегда плохо в программе., @Edgar Bonet
@EdgarBonet Вы правы, когда эта переменная (счетчик) не связана с классом, который будет создан (я так и предполагал, но я обновлю свой ответ). Если счетчик не связан, то вы абсолютно правы., @Michel Keijzers
Не могли бы вы объяснить, почему следует избегать простого использования глобальной переменной? Я не вижу недостатков с точки зрения использования памяти или производительности. Я бы сказал, что во многих ситуациях использование глобальной переменной просто прекрасно. На мой взгляд, усложнять тоже не очень элегантно., @Sim Son
@SimSon Нет никаких недостатков с точки зрения использования памяти или производительности, но если вы сделаете все переменные глобальными и у вас их тысячи, вы можете себе представить, какой это будет беспорядок. Это не так просто сделать в Arduino Uno с памятью 2 КБ, но лучше изолировать функциональность. На самом деле, одна из худших вещей в Arduino IDE заключается в том, что она лучше всего работает только с файлом Ino, и большинство людей не получают дальнейшего создания классов или отдельных файлов., @Michel Keijzers
@SimSon: см. [Являются ли глобальные переменные злом в Arduino?](https://arduinoprosto.ru/q/41493)., @Edgar Bonet
@MichelKeijzers хорошо, так что, как справиться с такой проблемой, это скорее личное решение. Я привык иметь несколько глобальных переменных, и я думаю, что это очень читабельно (огромная программа с «тысячами» глобальных переменных все равно будет сложной). Спасибо за разъяснение, @Sim Son
@EdgarBonet спасибо, я уже знаю этот вопрос., @Sim Son
@SimSon В вашем случае вы можете использовать статику, потому что вы будете использовать переменную только в этой одной функции. Или оставьте его глобальным (чем вы также можете использовать его в разных функциях, когда решите разделить функцию цикла, когда она станет большой или когда вам это нужно в прерывании). В программе, которую я делаю, у меня есть классы с переменными внутри классов (называемых свойствами), но моя программа состоит из более чем 1000 строк., @Michel Keijzers
@MichelKeijzers Я буду иметь это в виду, спасибо!, @Sim Son
Кстати, есть недостаток в использовании неглобальных переменных, как упоминал Дункан С, глобальные переменные в куче и локальные переменные в стеке. Это означает, что они не будут учитываться в сводке, которую вы получите при построении скетча (поэтому вам придется вручную отслеживать размер локальной переменной (и параметры), чтобы знать, могут ли возникнуть проблемы с памятью., @Michel Keijzers
Глобальные переменные статически размещаются в секциях .data или .bss, как и статические
локальные переменные. Все они учитываются в сводке использования памяти во время сборки. Куча — это место, куда вы попадаете с помощью malloc()
и new
., @Edgar Bonet
@EdgarBonet Вы полностью правы (снова) ... в случае OP и глобальные, и статические будут учитываться в .data/.bss, а локальные (нестатические) попадают в стек., @Michel Keijzers
Мне не нравится идея "зациклиться на времени". Функция void loop(void)
уже вызывается из цикла, в котором можно предположить, что в нем есть какой-то другой код, пока мы не узнаем обратного. Идея «зацикливания через некоторое время» предотвращает выполнение этого другого кода, потому что он никогда не возвращается., @AaronD
@AaronD Правда ... на самом деле это даже не работает, как показывает Эдгар Боннет, после каждого вызова цикла в фреймворке вызывается некоторый код., @Michel Keijzers
Ответ Майкла, как обычно, был хорош. Позвольте мне дать вам еще немного информации:
В C/C++ (и в большинстве современных языков) переменные имеют "область действия" или область, в которой они определены.
Глобальный охват:
Переменные, объявленные на верхнем уровне вашей программы, имеют глобальную область действия и существуют на протяжении всей жизни вашей программы. Эти переменные обычно создаются в разделах .data или .bss.
Локальная область
Переменные, объявленные внутри функции, имеют локальную область действия. Если они не являются статическими
, они создаются при входе в функцию и удаляются при выходе из функции. Эти переменные создаются в стеке как часть кадра стека функции.
Область объекта
В C++ и других объектно-ориентированных языках экземпляры объектов имеют собственную область видимости, "переменные экземпляра". Каждый экземпляр объекта имеет свой собственный набор переменных экземпляра. (Подумайте об автомобилях и автомобильном радиоприемнике. У нас с вами может быть одна и та же модель автомобиля, но если я настрою радиостанцию своего автомобиля, а вы настроите радиоприемник своего автомобиля на другую станцию, каждый экземпляр автомобиля будет иметь разные настройки. для радиостанции.)
Статические переменные:
Статические переменные — это переменные, объявленные "статически". Это означает, что они создаются только один раз и сохраняются, даже если они объявлены внутри области видимости, такой как функция или экземпляр класса. Они имеют срок жизни глобальной переменной, но могут быть объявлены внутри функции или в объекте.
Цикл — это функция, поэтому переменные, объявленные внутри loop()
, являются локальными переменными. Они создаются заново каждый раз при вызове функции и удаляются при каждом выходе из функции.
+1 за отличное объяснение и предысторию, @Michel Keijzers
В вашем коде есть несколько ошибок. Во-первых, вы не инициализируете счетчик перед его использованием. Во-вторых, переменная counter имеет ограниченную область действия, она не имеет определения при выходе из цикла(). Если вы хотите, чтобы эта переменная осталась, объявите ее статической.
Обе ошибки исправлены например:
void loop(void)
{
static uint8_t counter = 0;
....
if (buttonPress)
counter = 0;
...
if (someCondition == true)
{
Serial.println(counter);
counter++
}
}
Примечание. Инициализация счетчика в 0 происходит только один раз, при последующих вызовах loop() счетчик сохраняет свое предыдущее значение.
- Справка по коду Си. Новая строка печатается в scanf, потому что я печатаю строку и int вместе. Нужна помощь. Новое в коде c
- устаревшее преобразование из строковой константы в 'char*'
- как быстро loop() работает в Arduino
- как отправить аргумент объектам ESP8266WebServer в функции
- Arduino синтаксический анализ строки с использованием sscanf
- Как я могу прервать задержку() при нажатии кнопки?
- Как создать проект ардуино с несколькими исходными файлами?
- Как преобразовать символ Unicode в «Unicode HEX Position» в Arduino
Этот вопрос лучше подходит для переполнения стека, поскольку он не связан с Arduino., @Michel Keijzers
Итак, основной цикл в Arduino ведет себя как любая обычная вызываемая функция? Я почему-то думал, что переменные, объявленные в main(), автоматически статичны и сохраняют свое значение., @Sören
Нет, это не так, на самом деле loop() - это просто функция, внутри которой есть while(true) или while(1)., @Michel Keijzers
Большое спасибо за объяснение и возможные решения., @Sören
Просто, чтобы получить это прямо: С arduino main loop() — это просто обычная C-функция со всеми последствиями, касающимися области видимости переменных. Но в C-программе переменная, объявленная в main(), сохраняет свое значение. Так что, если бы я использовал простой C для своей программы вместо среды Arduino, переменная *counter* работала бы без объявления *static*. Конечно, тогда операторы if будут находиться в цикле while(1)., @Sören
Да, вы правы (если я правильно понимаю ваши предложения). Я не совсем уверен, но я предполагаю, что main(), который является обязательным для c, создается IDE и невидим для пользователя, он что-то инициализирует, затем вызывает настройку (один раз), затем, возможно, выполняются какие-то другие проверки, чем `пока (истина) { цикл(); } называется,, @Michel Keijzers
(кстати, если ответ полезен, проголосуйте за него (щелкнув стрелку «вверх»), если ответ (или дан лучший ответ, который решает вашу проблему), примите ответ., @Michel Keijzers
@MichelKeijzers: Вот [
main()
](https://github.com/arduino/ArduinoCore-avr/blob/1.6.23/cores/arduino/main.cpp#L33)., @Edgar Bonet@EdgarBonet Спасибо за этот код., @Michel Keijzers
@Sören _ «Но в C-программе переменная, объявленная в main(), сохраняет свое значение». Так же, как и для
loop()
Arduino, да и вообще для любой другой функции. (Конечно, одно отличие состоит в том, что как только вы покидаетеmain()
, ваша программа завершает работу, и не имеет значения, что происходит с переменными.), @marcelm