В чем разница между объявлением переменной вне цикла и объявлением статики внутри цикла?

Это два способа хранения переменной вне цикла (или любой функции).

Во-первых, я могу объявить его с глобальной областью действия вне цикла:

void setup()
{
    Serial.begin(9600);
}

int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    delay(250);
}

Я также могу объявить его статическим внутри цикла:

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    static int count = 0;

    Serial.println(count);
    count++;

    delay(250);
}

Какая разница, если таковая имеется?

, 👍10


4 ответа


Лучший ответ:

10

Самое основное различие заключается в области действия.

В первом случае вы объявляете глобальную переменную. Это переменная, доступная в любой области после ее определения.

void setup()
{
    Serial.begin(9600);
}

void inc();
int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

void inc() // Можно редактировать значение count
{
  count=count+1;
};

Во втором случае вы объявляете статическую переменную с локальной областью действия. Переменная будет сохраняться на протяжении всего выполнения программы, как и глобальные переменные, но будет доступна только в блоке кода, в котором она объявлена. Это тот же пример, только с одним изменением. count теперь объявлен как статическая переменная внутри loop.

void inc();

void loop()
{
    static int count = 0;
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

Это не будет компилироваться, так как функция inc() не имеет доступа к count.

Глобальные переменные, какими бы полезными они ни казались, имеют некоторые подводные камни. Они могут даже нанести ущерб, когда дело доходит до написания программ, которые могут взаимодействовать с физическим окружением. Это очень простой пример того, что вполне вероятно произойдет, как только программы станут больше. Функция может непреднамеренно изменить состояние глобальной переменной.

void setup()
{
    Serial.begin(9600);
}
void another_function();
int state=0;

void loop()
{
    // Продолжаем переключать состояние
    Serial.println(state);
    delay(250);
    state=state?0:1;

    // Какой-то несвязанный вызов функции
    another_function();
}

void another_function()
{
  //Непреднамеренно меняет состояние
  state=1;

}

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

void setup()
{
    Serial.begin(9600);
}
void another_function();

void loop()
{
    static int state=0;

    // Продолжаем переключать состояние
    Serial.println(state);
    delay(250);
    state=state?0:1;

    // Какой-то несвязанный вызов функции
    another_function();
}

void another_function()
{
  // Приводит к ошибке времени компиляции. Экономит время.
  state=1;

}
,

-3

Согласно документации Atmel: "Если объявлена глобальная переменная, во время компоновки программы этой переменной будет присвоен уникальный адрес в SRAM".

Полная документация находится здесь (Совет №2 по глобальным переменным): http://www.atmel.com/images /doc8453.pdf

,

Разве оба примера не получат уникальный адрес в SRAM? Им обоим нужно упорствовать., @Cybergibbons

Да, на самом деле вы можете найти эту информацию в том же документе в совете № 6., @jfpoilpret


5

С функциональной точки зрения обе версии генерируют одинаковый результат, поскольку в обоих случаях значение count сохраняется между выполнениями loop() (либо потому, что это глобальная переменная или потому что она помечена как static и поэтому сохраняет свое значение).

Итак, решение о выборе сводится к следующим аргументам:

  1. Как правило, в информатике рекомендуется, чтобы ваши переменные были как можно более локальными с точки зрения области действия. Обычно это приводит к гораздо более ясному коду с меньшим количеством побочных эффектов и снижает вероятность того, что кто-то другой использует эту глобальную переменную и испортит вашу логику. Например, в первом примере другие области логики могут изменить значение count, тогда как во втором это может сделать только эта конкретная функция loop().
  2. Глобальные и статические переменные всегда занимают память, тогда как локальные переменные делают это только тогда, когда они находятся в области видимости. В приведенных выше примерах это не имеет значения (поскольку в одном вы используете глобальную, а в другом статическую переменную), но в более крупных и сложных программах это может быть, и вы можете сэкономить память, используя нестатические локальные переменные. Однако: если у вас есть переменная в логической области, которая выполняется очень часто, подумайте о том, чтобы сделать ее статической или глобальной, поскольку в противном случае вы теряете немного производительности каждый раз, когда эта логика введена область, так как требуется немного времени для выделения памяти для этого нового экземпляра переменной. Вам необходимо найти баланс между нагрузкой на память и производительностью.
  3. Также могут иметь значение и другие моменты, такие как лучший макет для статического анализа или оптимизация компилятором.
  4. В некоторых особых случаях могут возникнуть проблемы с непредсказуемым порядком инициализации статических элементов (не уверен в этом, сравните это ссылка хотя).

Источник: Похожая тема на arduino.cc

,

Повторный вход никогда не должен быть проблемой для Arduino, поскольку он не поддерживает параллелизм., @Peter Bloomfield

Истинный. Это было скорее общее замечание, но на самом деле не относящееся к Arduino. Я удалил этот бит., @Philip Allgaier

Статическая переменная, объявленная внутри области видимости, всегда будет существовать и использовать то же пространство, что и глобальная переменная! В коде OP единственная разница заключается в том, какой код может получить доступ к переменной. В scipe static будет доступен в той же области видимости., @jfpoilpret

@jfpoilpret Это, конечно, правда, и я вижу, что соответствующая часть моего ответа немного вводит в заблуждение. Исправлено., @Philip Allgaier


2

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

Перемещение определения count внутрь функции одновременно ограничивает ее область видимости ближайшим охватывающим набором {}es и увеличивает время жизни вызова функции ( он создается и уничтожается при входе и выходе из функции). Объявление его static также дает ему время жизни сеанса выполнения, которое существует с начала до конца сеанса выполнения, сохраняясь при вызовах функций.

Кстати: будьте осторожны при использовании инициализированной статики внутри функции, так как я видел, что некоторые версии компилятора gnu делают это неправильно. Автоматическая переменная с инициализатором должна создаваться и инициализироваться при каждом входе в функцию. Статика с инициализатором должна быть инициализирована только один раз, во время настройки выполнения, прежде чем функция main() получит управление (так же, как и глобальная). У меня была локальная статика, которая повторно инициализировалась при каждой записи функции, как если бы они были автоматическими, что неверно. Чтобы быть уверенным, протестируйте свой собственный компилятор.

,

Я не уверен, что понимаю, что вы имеете в виду о функции, объявляющей глобальную. Вы имеете в виду как «экстерн»?, @Peter Bloomfield

@PeterR.Bloomfield: я не уверен, о какой части моего поста вы спрашиваете, но я имел в виду два примера OP - первый, изначально глобальное определение, и второй, локальное статическое., @JRobert