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


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.