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


9

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

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

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

Самое основное отличие заключается в сфере применения.

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

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

void inc();
int count = 0;

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

    inc();

    delay(500);
}

void inc() //Can edit the value of 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()
{
    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Inadvertently changes state
  state=1;

}

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

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

void loop()
{
    static int state=0;

    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Results in a compile time error. Saves time.
  state=1;

}

5

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

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

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

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


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

Правда. Это было больше общего, но на самом деле не имеет значения для Arduino. Я убрал этот бит.
— Филипп Альгайер

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

1
@jfpoilpret Это, конечно, правда, и я вижу, что соответствующая часть в моем ответе была немного вводящей в заблуждение. Исправлено
— Филипп Альгайер

2

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

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

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


Я не уверен, что понимаю, что вы имеете в виду под функцией, объявляющей глобальный. Вы имеете в виду как extern?
— Питер Блумфилд

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

-3

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

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


4
Разве оба примера не заканчиваются уникальным адресом в SRAM? Они оба должны упорствовать.
— Cybergibbons

2
Да, на самом деле вы можете найти эту информацию в том же документе в совете № 6
— jfpoilpret
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.