Яка різниця між оголошенням змінної за межами циклу і оголошенням статичного всередині циклу?


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, оскільки воно не підтримує паралельність.
Пітер Блумфілд

Правда. Це було більше загальним моментом, але насправді не стосується Ардуїно. Я видалив цей шматочок.
Філіп Аллгайер

1
Статична змінна, оголошена всередині області, завжди буде існувати і використовуватиме той самий простір, що і глобальна змінна! У коді OP єдиною різницею є те, який код може отримати доступ до змінної. У снайпе статичні будуть доступні в тому ж обсязі.
jfpoilpret

1
@jfpoilpret Звичайно, це правда, і я бачу, що відповідна частина моєї відповіді була дещо оманливою. Виправлено це.
Philip Allgaier

2

Обидві змінні є статичними - вони зберігаються протягом усього сеансу виконання. Глобальний є видимим для будь-якої функції, якщо він оголошує - не визначає - глобальний, або якщо функція слідує за визначенням у тому самому блоці компіляції (файл + включає).

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

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


Я не впевнений, що я розумію, що ви маєте на увазі щодо функції, що оголошує глобальну. Ви маєте на увазі як extern?
Пітер Блумфілд

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

-3

Згідно з документацією Atmel: "Якщо оголошена глобальна змінна, унікальна адреса в SRAM буде присвоєна цій змінній у час зв'язку програми".

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


4
Чи не будуть обидва приклади мати унікальну адресу в SRAM? Їм обом потрібно наполягати.
Кібергіббони

2
Так, фактично ви можете знайти цю інформацію в тому самому документі в підказці №6
jfpoilpret
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.