Объявление переменной внутри основного цикла

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

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;

Затем он увеличивается, как и ожидалось.

Несмотря на то, что я нашел решение, мне очень хотелось бы понять, в чем заключалась проблема с моим первым подходом.

, 👍3

Обсуждение

Этот вопрос лучше подходит для переполнения стека, поскольку он не связан с 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


3 ответа


0

Проблема в том, что вы объявляете новую переменную. Таким образом, несмотря на то, что имя такое же, локальная переменная уничтожается в конце цикла и создается заново в начале.

На самом деле хорошего решения нет:

  • Глобальные переменные лучше не использовать. Их можно рассматривать как «одиночки», например переменные, которые нужны во многих местах кода и встречаются только один раз.
  • Как вы выяснили, статика решает проблему. Я бы использовал этот вариант, хотя лично я не сторонник статических переменных.
  • Еще один способ — поместить цикл через некоторое время, чтобы получить:

    пустой цикл(пустой) { счетчик 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


4

Ответ Майкла, как обычно, был хорош. Позвольте мне дать вам еще немного информации:

В C/C++ (и в большинстве современных языков) переменные имеют "область действия" или область, в которой они определены.

Глобальный охват:

Переменные, объявленные на верхнем уровне вашей программы, имеют глобальную область действия и существуют на протяжении всей жизни вашей программы. Эти переменные обычно создаются в разделах .data или .bss.

Локальная область

Переменные, объявленные внутри функции, имеют локальную область действия. Если они не являются статическими, они создаются при входе в функцию и удаляются при выходе из функции. Эти переменные создаются в стеке как часть кадра стека функции.

Область объекта

В C++ и других объектно-ориентированных языках экземпляры объектов имеют собственную область видимости, "переменные экземпляра". Каждый экземпляр объекта имеет свой собственный набор переменных экземпляра. (Подумайте об автомобилях и автомобильном радиоприемнике. У нас с вами может быть одна и та же модель автомобиля, но если я настрою радиостанцию своего автомобиля, а вы настроите радиоприемник своего автомобиля на другую станцию, каждый экземпляр автомобиля будет иметь разные настройки. для радиостанции.)

Статические переменные:

Статические переменные — это переменные, объявленные "статически". Это означает, что они создаются только один раз и сохраняются, даже если они объявлены внутри области видимости, такой как функция или экземпляр класса. Они имеют срок жизни глобальной переменной, но могут быть объявлены внутри функции или в объекте.


Цикл — это функция, поэтому переменные, объявленные внутри loop(), являются локальными переменными. Они создаются заново каждый раз при вызове функции и удаляются при каждом выходе из функции.

,

+1 за отличное объяснение и предысторию, @Michel Keijzers


2

В вашем коде есть несколько ошибок. Во-первых, вы не инициализируете счетчик перед его использованием. Во-вторых, переменная counter имеет ограниченную область действия, она не имеет определения при выходе из цикла(). Если вы хотите, чтобы эта переменная осталась, объявите ее статической.

Обе ошибки исправлены например:

void loop(void)
{
    static uint8_t counter = 0;
    ....
    if (buttonPress)
        counter = 0;
    ...
    if (someCondition == true)
    {
        Serial.println(counter);
        counter++
    }
}

Примечание. Инициализация счетчика в 0 происходит только один раз, при последующих вызовах loop() счетчик сохраняет свое предыдущее значение.

,