Чи зло в Ардуїно глобальні змінні?


24

Я відносно новий у програмуванні, і багато з найкращих практик кодування, які я читаю, ефективно стверджують, що існує дуже мало вагомих причин використовувати глобальну змінну (або що найкращий код взагалі не має глобальних даних).

Я зробив усе можливе, щоб це пам’ятати, коли писав програмне забезпечення для створення інтерфейсу Arduino зі SD-карткою, спілкувався з комп’ютером та запускав контролер двигуна.

В даний час я маю 46 глобальних орієнтирів для приблизно 1100 рядків коду "початкового рівня" (жоден рядок не має більше однієї дії). Це хороше співвідношення чи варто дивитись на зменшення його більше? Також які практики я можу використовувати для подальшого зменшення кількості глобальних компаній?

Я запитую це, тому що я конкретно переймаюся кращими методами кодування на продуктах Arduino, а не комп'ютерним програмуванням взагалі.


2
В Arduino ви не можете уникнути глобальних змінних. Кожне оголошення змінної за межами функції / методу є глобальним. Отже, якщо вам потрібно поділити значення між функціями, вони повинні бути глобальними, якщо ви не хочете передавати кожне значення як аргумент.

16
@LookAlterno Помилка, чи не можете ви писати заняття в Ардуніо, оскільки це просто C ++ із дивними макросами та бібліотеками? Якщо так, не кожна змінна є глобальною. Навіть у С зазвичай вважається найкращою практикою віддавати перевагу передачі змінних (можливо, всередині структур) у функції, а не глобальним змінним. Це може бути менш зручним для невеликої програми, але зазвичай окупається, коли програма стає все більшою і складнішою.
Muzer

11
@LookAlterno: "Я уникаю" і "ти не можеш" - це дуже різні речі.
Гонки легкості з Монікою

2
Насправді, деякі вбудовані програмісти забороняють локальні змінні та вимагають глобальних змінних (або функціонують статичні змінні). Коли програми невеликі, це може полегшити їх аналіз як просту машину; тому що змінні ніколи не перезаписують одна одну (тобто: як вони складають і купують змінні).
Роб

1
Статичні та глобальні змінні пропонують перевагу в знанні споживання пам'яті під час компіляції. Якщо ардуїно має дуже обмежену пам’ять, це може бути перевагою. Початківцю досить легко вичерпати наявну пам’ять і зазнати непростених невдач.
антипатерн

Відповіді:


33

Вони самі по собі не є злими , але вони, як правило, надмірно використовуються там, де немає вагомих причин використовувати їх.

Існує багато випадків, коли глобальні змінні є вигідними. Тим більше, що програмування Arduino під капотом сильно відрізняється від програмування ПК.

Найбільша користь для глобальних змінних - статичне розподілення. Особливо з великими та складними змінними, такими як екземпляри класу. Динамічне розподіл (використання newтощо) нахмуриться через брак ресурсів.

Крім того, ви не отримуєте одного дерева викликів, як у звичайній програмі C (одна main()функція, яка викликає інші функції) - замість цього ви ефективно отримуєте два окремих дерева ( setup()функції виклику, потім loop()функції виклику), що означає, що іноді глобальними змінними є єдиний спосіб досягти своєї мети (тобто, якщо ви хочете використовувати її в обох setup()і loop()).

Так що ні, вони не злі, і на Arduino вони мають більше і краще використання, ніж на ПК.


Гаразд, що робити, якщо це те, що я використовую лише loop()(або в кількох функціях, що викликаються loop())? було б краще їх встановити іншим способом, ніж їх визначати на початку?
ATE-ENGE

1
У такому випадку я б, мабуть, визначив їх у циклі () (можливо, як staticніби мені знадобилося, щоб вони зберегли своє значення в ітераціях) і передали їх через параметри функції вниз по ланцюгу виклику.
Majenko

2
Це один спосіб, або передайте це як посилання: void foo(int &var) { var = 4; }і foo(n);- nзараз 4.
Majenko

1
Заняття можуть бути виділені стеком. Справді, не дуже добре ставити великі екземпляри на стек, але все ж.
JAB

1
@JAB Будь-що можна виділити стеком.
Majenko

18

Дуже складно дати остаточну відповідь, не побачивши свого фактичного коду.

Глобальні змінні не є злими, і вони часто мають сенс у вбудованій обстановці, де ти зазвичай робиш багато апаратного доступу. У вас є лише чотири UARTS, лише один порт I2C тощо. Тому має сенс використовувати глобалі для змінних, прив'язаних до конкретних апаратних ресурсів. І дійсно, Arduino бібліотека ядра робить це: Serial, Serial1і т.д. є глобальними змінними. Також змінна, що представляє глобальний стан програми, зазвичай є глобальною.

В даний час я маю 46 глобальних компаній для приблизно 1100 рядків [коду]. Це хороше співвідношення [...]

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

І все-таки 46 глобальних здається мені трохи високими. Якщо деякі з них містять постійні значення, кваліфікуйте їх як const: компілятор зазвичай оптимізує їх сховище. Якщо будь-яка з цих змінних використовується лише всередині однієї функції, зробіть її локальною. Якщо ви хочете, щоб його значення зберігалося між дзвінками до функції, визначте її як static. Ви також можете зменшити кількість "видимих" глобальних груп, згрупувавши змінні разом у класі та маючи один глобальний екземпляр цього класу. Але робіть це лише тоді, коли є сенс складати речі. Створення великого GlobalStuffкласу заради наявності лише однієї глобальної змінної не допоможе зробити ваш код яснішим.


1
Добре! Я не знав про те, staticчи є у мене змінна, яка використовується лише в одній функції, але отримую нове значення кожного разу, коли функція викликається (як var=millis()), чи варто це робити static?
ATE-ENGE

3
Ні static- це лише тоді, коли вам потрібно зберегти значення. Якщо першим, що ви робите в функції, є встановлення значення змінної на поточний час, не потрібно зберігати старе значення. Все, що робить статика в цій ситуації - це витрачати пам'ять.
Андрій

Це дуже чітка відповідь, яка виражає обидва моменти на користь глобалістів і скептицизм. +1
підкреслення_d

6

Основна проблема глобальних змінних - підтримка коду. Читаючи рядок коду, легко знайти оголошення змінних, переданих як параметр або оголошених локально. Знайти декларацію глобальних змінних не так просто (часто це вимагає і IDE).

Коли у вас багато глобальних змінних (40 - це вже багато), стає важким чітке ім'я, яке не надто довго. Використання простору імен - це спосіб уточнити роль глобальних змінних.

Поганий спосіб імітувати простори імен у C:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

У процесорі Intel або arm, доступ до глобальних змінних повільніше, ніж до інших змінних. На ардуїно це, мабуть, навпаки.


2
На AVR глобальні глобальні пам'яті знаходяться на оперативній пам’яті, тоді як більшість нестатичних локальних пристроїв виділяються в регістри процесора, що робить їх доступ швидшим.
Едгар Бонет

1
Проблема насправді не в пошуку декларації; можливість швидко зрозуміти код - це важливо, але в кінцевому рахунку є другорядним у відношенні того, що він робить - і справжня проблема полягає у знаходженні того, який саме шкідник, у якій невідомій частині вашого коду, може використовувати глобальну декларацію, щоб робити дивні речі з предметом, який ви залишили під відкритим небом для кожного робити все, що завгодно.
підкреслюй_d

5

Хоча я б не використовував їх при програмуванні для ПК, для Arduino вони мають деякі переваги. Більшість, якщо вже було сказано:

  • Немає динамічного використання пам'яті (створення прогалин у обмеженому просторі купи Arduino)
  • Доступні скрізь, тому не потрібно передавати їх як аргументи (що коштує стеку місця)

Крім того, в деяких випадках, особливо для продуктивності, може бути корисним використання глобальних змінних, а не створення елементів, коли це потрібно:

  • Для зменшення розривів у купі простору
  • Динамічне розподілення та / або звільнення пам'яті може зайняти значний час
  • Змінні можуть бути "повторно використані", як перелік елементів глобального списку, які використовуються з декількох причин, наприклад, один раз як буфер для SD, а пізніше як тимчасовий буфер рядків.

5

Як і у всьому (окрім готів, які є справді злими) глобалісти мають своє місце.

Наприклад, якщо у вас є послідовний порт налагодження або файл журналу, до якого потрібно писати звідусіль, тоді це часто має сенс зробити глобальним. Аналогічно, якщо у вас є критична частина інформації про стан системи, то її глобальне використання найчастіше є найпростішим рішенням. Немає сенсу мати значення, яке вам доведеться передати кожній функції в програмі.

Як зазначають інші, 46 здається чималим лише за понад 1000 рядків коду, але не знаючи деталей того, що ви робите, важко сказати, перестаєте ви їх використовувати чи ні.

Однак для кожного глобального задайте собі кілька важливих питань:

Чи ім'я чітке та конкретне, щоб я випадково не намагався використовувати те саме ім’я десь ще? Якщо не змінити ім'я.

Чи потрібно це коли-небудь змінювати? Якщо ні, то подумайте про те, щоб зробити це const.

Чи потрібно це бачити скрізь або це лише глобальне, щоб значення зберігалося між викликами функцій? Подумайте про те, як зробити його локальним для функції та використовувати ключове слово статичний.

Чи дійсно все буде погано, якщо це зміниться за допомогою фрагмента коду, коли я не буду обережним? Наприклад, якщо у вас є дві пов'язані змінні, скажімо, ім'я та номер ідентифікатора, які є глобальними (див. попередню примітку про використання глобальної, коли вам потрібна інформація майже всюди), то якщо одна з них буде змінена без інших неприємних речей, це може статися. Так, ви можете бути просто обережними, але іноді добре трохи дотримуватися обережності. наприклад, помістіть їх у інший .c-файл, а потім визначте функції, які встановлюють обидві їх одночасно і дозволяють читати їх. Потім ви включаєте лише функції в пов'язаний файл заголовка, таким чином, решта вашого коду може отримати доступ до змінних лише за допомогою визначених функцій, і тому не можна зіпсувати речі.

- оновлення - я щойно зрозумів, що ви запитували про найкращу практику щодо Arduino, а не про загальне кодування, і це скоріше загальна відповідь на кодування. Але якщо чесно, різниці немає, хороша практика - це хороша практика. startup()І loop()структура Arduino означає , що ви повинні використовувати глобальні трохи більше , ніж інші платформи , в деяких ситуаціях , але це на самому ділі не зміниться, ви завжди в кінцевому підсумку прагне до кращого ви можете зробити в рамках обмежень платформи , незалежно від того , що платформа є.


Я нічого не знаю про Arduinos, але роблю багато розробок настільних та серверів. Є одне прийнятне використання (IMHO) для gotos, і це вирватися з вкладених циклів, це набагато чистіше і легше зрозуміти, ніж альтернативи.
Наполегливість

Якщо ви вважаєте goto, що це зло, перевірте код Linux. Можливо, вони так само злі, як try...catchблоки, коли використовуються як такі.
Дмитро Григор’єв

5

Вони злі? Можливо. Проблема глобальних мереж полягає в тому, що до них можна отримати доступ і змінити в будь-який момент часу будь-якою функцією або фрагментом коду, що виконується, без обмежень. Це може призвести до ситуацій, які, скажімо, важко відстежити і пояснити. Таким чином, бажано мінімізувати кількість глобальних балів, якщо можливо, повернути суму до нуля.

Чи можна їх уникнути? Майже завжди так. Проблема з Arduino полягає в тому, що вони змушують вас до цього двостороннього підходу, в якому вони беруть на себе вас setup()і вас loop(). У цьому конкретному випадку у вас немає доступу до сфери функції виклику цих двох функцій (ймовірно main()). Якби у вас це було, ви змогли б позбутися від усіх глобальних та використовувати натомість місцевих жителів.

Зобразіть наступне:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

Це, мабуть, більш-менш те, як виглядає основна функція програми Arduino. Змінні, які вам потрібні і в, setup()і в loop()функції, тоді бажано оголошуватись би в межах main()функції, а не в глобальній області. Потім вони можуть бути доступними для двох інших функцій за допомогою передачі їх у якості аргументів (використовуючи покажчики, якщо потрібно).

Наприклад:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Зауважте, що в цьому випадку вам також потрібно змінити підпис обох функцій.

Оскільки це може бути неможливим чи бажаним, я бачу дійсно лише один спосіб вилучити більшість глобальних програм з програми Arduino, не змінюючи примусової структури програми.

Якщо я пам'ятаю правильно, ви прекрасно можете використовувати C ++ під час програмування для Arduino, а не C. Якщо ви ще не знайомі з OOP (об'єктно-орієнтоване програмування) або C ++, це може зайняти деяке звикання, а деякі читання.

Моєю пропозицією було б створити клас програми та створити єдиний глобальний примірник цього класу. Класом слід вважати план об'єктів.

Розглянемо наступний приклад програми:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Вуаля, ми позбулися майже всіх глобальних. Функціями, в яких ви б почали додавати логіку програми, були б Program::setup()і Program::loop()функції. Ці функції мають доступ до змінних, що стосуються конкретних примірників, myFirstSampleVariableі mySecondSampleVariableтоді як традиційні setup()та loop()функції не мають доступу, оскільки ці змінні були позначені класом приватними. Ця концепція називається інкапсуляцією даних або приховуванням даних.

Навчити вас OOP та / або C ++ трохи не вдається відповісти на це запитання, тому я зупинюсь тут.

Підводячи підсумок: глобальних глобалів слід уникати, і майже завжди можна різко зменшити кількість глобалів. Також, коли ви програмуєте для Arduino.

Найголовніше, я сподіваюся, що моя відповідь вам дещо корисна :)


Ви можете визначити свій власний main () у ескізі, якщо хочете. Ось як виглядає акція
per1234

Сингл - це фактично глобальний, просто одягнений у надзвичайно заплутаний спосіб. У нього такі ж недоліки.
patstew

@patstew Ви не хочете пояснити мені, як ви вважаєте, що це має ті ж самі недоліки? На мою думку, це не так, оскільки ви можете використовувати інкапсуляцію даних на вашу користь.
Аржен

@ per1234 Дякую! Я точно не є експертом з Ардуїно, але, мабуть, моя перша пропозиція могла б спрацювати і тоді.
Аржен

2
Ну, це все ще глобальна держава, до якої можна отримати доступ будь-де в програмі, ви просто отримаєте доступ до неї, Program::instance().setup()а не через globalProgram.setup(). Розміщення пов'язаних глобальних змінних в одному просторі класів / структура / імен може бути корисним, особливо якщо їм потрібна лише пара пов'язаних функцій, але однотонний шаблон нічого не додає. Іншими словами, static Program p;має глобальне сховище та static Program& instance()має глобальний доступ, що становить те саме, що просто Program globalProgram;.
patstew

4

Глобальні змінні ніколи не є злими . Загальне правило проти них - це просто милість, яка дозволяє вам вижити досить довго, щоб отримати досвід для прийняття кращих рішень.

Що таке глобальна змінна, є притаманним припущенням, що є лише одна річ (не має значення, якщо ми говоримо про глобальний масив або карту, яка може містити кілька речей, що все ще містить припущення, що існує лише один такий список чи відображення, а не кілька незалежних).

Отже, перш ніж скористатися глобальним, ви хочете запитати себе: чи можливо, я коли-небудь захочу використовувати більше, ніж одну з цього? Якщо це виявиться правдою внизу лінії, вам доведеться змінити код, щоб неглобалізувати цю річ, і ви, ймовірно, з'ясуєте, що інші частини вашого коду залежать від цього припущення про унікальність, тож ви Також доведеться їх виправити, і процес стає виснажливим і схильним до помилок. "Не користуйтеся глобальними" викладається, тому що зазвичай уникнути глобальних витрат досить спочатку, і це дозволяє уникнути можливості платити великі витрати згодом.

Але спрощення припущень про те, що глобальні можливості дозволяють також зробити ваш код меншим, швидшим і використовувати менше пам'яті, тому що йому не потрібно обходити поняття, яку саме річ він використовує, не потрібно робити опосередкованість, не обов'язково врахуйте, що потрібна річ може не існувати і т. д. Якщо ви вбудовуєтесь, ви швидше обмежуєтесь розміром коду та / або процесорним часом та / або пам'яттю, ніж на ПК, тому ці заощадження можуть мати значення. І багато вбудованих додатків також мають більшу жорсткість у вимогах - ви знаєте, що ваш чіп має лише одну певну периферію, користувач не може просто підключити інший до USB-порту чи чогось іншого.

Ще одна поширена причина, коли потрібно більше ніж те, що здається унікальним, - це тестування - тестувати взаємодію між двома компонентами простіше, коли можна просто передати тестовий екземпляр якогось компонента функції, тоді як спроба змінити поведінку глобального компонента - це хитріше пропозиція. Але тестування у вбудованому світі, як правило, сильно відрізняється від інших, тому це може не стосуватися вас. Наскільки я знаю, у Arduino взагалі немає тестової культури.

Тому йдіть вперед і користуйтеся глобальними, коли вони здаються вартіми. Поліцейські з кодом не прийдуть і заберуть вас. Просто знайте, що неправильний вибір може призвести до набагато більше роботи для вас в дорозі, тому якщо ви не впевнені ...


0

Чи грішні глобальні змінні в Ардуїно?

ніщо по суті не є зло, включаючи глобальні змінні. Я б охарактеризував це як "необхідне зло" - це може полегшити ваше життя, але до нього слід підходити обережно.

Також які практики я можу використовувати для подальшого зменшення кількості глобальних компаній?

використовувати функції обгортки для доступу до глобальних змінних. тож ви принаймні керуєте нею з точки зору сфери.


3
Якщо ви використовуєте функції обгортки для доступу до глобальних змінних, ви також можете помістити свої змінні всередину цих функцій.
Дмитро Григор’єв
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.