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

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

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

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

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

20
@LookAlterno: "Я уникаю" , а "ви не можете" - це дуже різні речі.
додано Автор James Hopkin, джерело
Глобальні змінні на Arduino менш злі на ПК. Я бачив людей, які оголошують змінну циклу i як глобальну змінну. TBH Я намагаюсь уникати їх, але вони мають місце в управлінні пам'яттю.
додано Автор Thomas Myron, джерело
@LookAlterno - Найбільша проблема з глобальними змінами - це погані програмісти. Ви повинні передавати будь-яке значення як аргумент (спробуйте зберегти його менше 5), тому що це розділяє ваш код і дозволяє повторно використовувати. Копіювання та вставлення функцій разом у програми дуже легко, але лише у тому випадку, якщо ви уникаєте глобалів якомога більше. Він також запобігає монолітним функціям, якщо ви передаєте в 20 аргументів функцію лінії 2000, то вам слід переробити його.
додано Автор Thomas Myron, джерело
Насправді, деякі вбудовані програмісти забороняють локальні змінні і вимагають замість цього глобальних змінних (або статичних змінних, що мають функціональність). Коли програми невеликі, це може зробити його простішим для аналізу як простий державний апарат; тому що змінні ніколи не перезаписують один одного (тобто: як вони складають і купу виділяють змінні).
додано Автор Hououin Kyouma, джерело
@LookAlterno Err, чи не можете ви писати класи в Ардуніо, оскільки це просто C ++ з дивними макросами та бібліотеками? Якщо так, не кожна змінна глобальна. І навіть у С, зазвичай вважається найкращим досвідом, щоб перевага передавальних змінних (можливо, всередині структур) на функції, а не глобальні змінні. Може бути менш зручним для невеликої програми, але зазвичай вона окупається, коли програма стає все більш і більш складною.
додано Автор sarath, джерело
У Arduino ви не можете уникнути глобальних змінних. Кожна декларація змінного виходу за межі функції/методу глобальна. Отже, якщо вам потрібно розділити значення між функціями, вони повинні бути globals, якщо ви не хочете передавати будь-яке значення як аргумент.
додано Автор user31481, джерело
@Muzer Я використовую лише C ++ класи для розробки бібліотек, а також, більш великі програми. Я уникаю C ++ з причин.
додано Автор user31481, джерело
додано Автор Brian, джерело
@LookAlterno Це загальноприйняте істинність C/C ++ (незважаючи на це), що не є специфічним для Arduino взагалі, і як ви самі зазначили, ні в якому разі не приводить до висновку, що глобальних змінних не можна уникнути; вони можуть і часто повинні. Якщо відповідь на це запитання може відрізнятися, коли платформа Arduino не на рівні мови, а скоріше в шаблонах дизайну, які мають сенс на платформі, і чи є вони глоабами бажаними.
додано Автор Not that Charles, джерело
@ Мазер, так, завжди вважали, що це обробка. TIL
додано Автор Sonic Splasher, джерело
Статичні та глобальні змінні забезпечують перевагу знання споживання пам'яті в час компіляції. З ардюно, що має дуже обмежену пам'ять, це може бути перевагою. Для початківця досить легко вичерпати доступну пам'ять і відчути незрозумілі невдачі.
додано Автор mgpugne, джерело
Hey All: Це стало набагато популярнішим, ніж очікувалося. Я багато чому навчився за останні 9 місяців і хотів би зробити це як корисне для "інших моїх", наскільки це можливо. Люди оцінять це, якщо я додав цей пост із посиланнями на підручники/ідеї, які допомогли мені, як новачкові, вдосконалити свій код? Я до сих пір не впевнений 100% в правилі біржі, тому я думав, що попрошу 1-го.
додано Автор cocco, джерело

8 Відповіді

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

Є багато разів, коли глобальні змінні є вигідними. Особливо після програмування Arduino, під капотом, сильно відрізняється від програмування ПК.

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

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

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

26
додано
По правді, я знехтував надто швидко і зібрав деякі коментарі з питання про невикористання класів.
додано Автор JaanusSiim, джерело
Класи можуть бути розподілені за стек. Напевно, не дуже добре розміщувати великі екземпляри у стекі, але все-таки.
додано Автор JaanusSiim, джерело
@ JAB Усе може бути розподіленим стеком.
додано Автор Majenko, джерело
У такому випадку я б, напевно, визначив їх у циклі() (можливо, як static , якщо мені потрібно було зберегти їх значення через ітерації) і передавати їх через параметри функції вниз по ланцюжку дзвінків.
додано Автор Majenko, джерело
Це один спосіб, або передайте його як довідку: void foo (int & var) {var = 4; } і foo (n); - n тепер 4.
додано Автор Majenko, джерело
Однією з причин, які я чув, щоб уникнути глобальних змінних, більше пов'язана з іншою системою, ніж Arduino, глобальні змінні можуть бути викриті зовнішньою або поза межами самої програми. Це твердження C ++ говорить про це краще. "У мові C немає глобального ключового слова. Змінні, оголошені поза функцією, мають" область файлу ", тобто вони є видимими у файлі". Це викриває змінний простір для модифікації поза межами програми перед запуском, як у випадку зі статикою, де перед роботою може змінюватись змінний простір файлів, створюючи потенційні ризики для безпеки
додано Автор Clems, джерело
Гаразд, я думаю, що можу це зробити. Якщо у мене є (наприклад) static var n , визначена в loop() . Якщо я передаю n в foo (var) і в foo (var) встановлюю n = [new value] . Чи потрібно мені мати [нове значення] і мати n = [нове значення] в loop() ?
додано Автор cocco, джерело
Гаразд, якщо це щось, що я використовую лише в loop() (або в декількох функціях, які називаються в loop() )? чи було б краще налаштувати їх іншим способом, ніж визначати їх на початку?
додано Автор cocco, джерело
Факт неможливості зв'язку між setup() і loop() сам по собі є відмінною причиною для використання більш стандартного tooluite і старого доброго main() або _main() .
додано Автор Megatron, джерело

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

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

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

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

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

16
додано
Номер static використовується лише для того, коли потрібно зберегти значення. Якщо перше, що ви виконуєте в функції, встановлює значення змінної до поточного часу, то не потрібно зберігати попереднє значення. Все, що статичне робить у цій ситуації, витрачає пам'ять.
додано Автор Phil Hannent, джерело
Це дуже добре обґрунтована відповідь, яка виражає обидва моменти на користь, а також скептицизм щодо глобалів. +1
додано Автор Not that Charles, джерело
Добре, дякую за роз'яснення.
додано Автор cocco, джерело
В порядку! Я не знав про static , якщо у мене є змінна, яка використовується тільки в одній функції, але одержує нове значення кожного разу, коли викликається функція (наприклад, var = millis() ) я повинен зробити цей static ?
додано Автор cocco, джерело

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

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

Бідним способом імітувати простір імен в C є:

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

Про процесор Intel або руку доступ до глобальних змінних відбувається повільніше, ніж інші змінні. Це, напевно, протилежне ардуіно.

5
додано
На AVR глобальні пристрої знаходяться на оперативній пам'яті, тоді як більшість нестатичних місцевих жителів розподіляються на регістри процесора, що робить їх швидшим.
додано Автор Sprogz, джерело
Проблема полягає не в тому, щоб знайти декларацію; Можливість швидко зрозуміти цей код є важливим, але в кінцевому підсумку є вторинним до того, що він робить - і там реальна проблема полягає в тому, щоб виявити, який недолік, в якому невідома частина вашого коду вміє використовувати глобальну декларацію, щоб зробити дивні речі з об'єктом, що ви залишили на відкритому повітрі, щоб усі могли робити все, що завгодно.
додано Автор Not that Charles, джерело

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

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

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

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

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

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

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

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

Чи є ім'я чітким та конкретним, так що я випадково не намагаюся використовувати одне і те ж ім'я в іншому місці? Якщо не змінити назву.

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

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

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

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

4
додано
Якщо ви думаєте, що gooto є зла, перевірте код Linux. Можливо, вони так само злі, як блоки try ... catch , коли вони використовуються як такі.
додано Автор nreich, джерело
Я нічого не знаю про Arduinos, але роблю багато настільних і серверних рішень. Існує одне прийнятне використання (IMHO) для goto s, і це означає вирватися з вкладеного циклу, це набагато простіше і простіше зрозуміти, ніж альтернативи.
додано Автор sagelynaive, джерело

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

Чи їх можна уникнути? Майже завжди так. Проблема з 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() функції не мають доступу, оскільки ці змінні були позначені класу private. Це поняття називається інкапсуляція даних або приховування даних.

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

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

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

3
додано
Добре, це глобальна статистика, доступ до якої можна отримати будь-де в програмі, ви просто отримуєте доступ до неї за допомогою Program :: instance (). Setup() замість globalProgram.setup() . Встановлення суміжних глобальних змінних в один клас/структура/простір імен може бути корисним, особливо якщо вони потрібні лише кількома пов'язаними функціями, але шаблон singleton не містить нічого. Іншими словами, статична програма p; має глобальне сховище, а статична програма & instance() має глобальний доступ, який дорівнює такому, як просто Program globalProgram; .
додано Автор Tomas Andrle, джерело
Сингтон дійсно глобальний, просто одягнений додатково заплутаним способом. Він має ті самі недоліки.
додано Автор Tomas Andrle, джерело
Ви можете визначити власний основний() у ескізі, якщо хочете. Ось що виглядає фондовий: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
додано Автор per1234, джерело
@patstew Ти не проти, пояснивши мені, як ти відчуваєш, що він має ті самі недоліки? На мій погляд, це не так, оскільки ви можете використовувати інкапсуляцію даних на свою користь.
додано Автор gebeyaw, джерело
@ per1234 Дякую! Я, безумовно, не є експертом Arduino, але я думаю, що моя перша пропозиція може працювати і тоді.
додано Автор gebeyaw, джерело
@patstew Я так не думав про це. Я думаю, ти правий. Я буду модифікувати свій пост. Це також полегшить його розуміння.
додано Автор gebeyaw, джерело
@Граам Мені цікаво дізнатись, про яку дисципліну ви говорите? Я думаю, у мене є ідея про те, про що ви говорите, але дисципліна не збирається створювати "добре вбудований код". Шаблон accessor/muator іноді може мати невелику вартість продуктивності залежно від того, як ви встановили код, узгоджений. Проте багато хто дорогі операції можуть бути оптимізовані компілятором.
додано Автор gebeyaw, джерело
Як довгий час вбудований кодер, я досить регулярно готую людей з чавунного правила "Global Variables Bad". Передавання аргументів завжди робить код повільним. На ПК це майже несуттєво, але в вбудованому світі це все ще дуже велика справа. Шаблон accessor/muator - це спосіб гарантувати інкапсуляцію звуку за рахунок часу обробки. Цілком можливо, щоб написати добре вбудований код із глобальними змінами, але вам потрібно застосувати дисципліну до того, як ви його розробляєте, і чітко вкажіть, як інші люди повинні його використовувати.
додано Автор Maxim Krizhanovsky, джерело

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

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

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

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

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

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

2
додано

Чи глобальні змінні зла в Arduino?

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

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

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

0
додано
Якщо ви користуєтеся функціями обгортки для доступу до глобальних змінних, ви також можете помістити свої змінні в ці функції.
додано Автор nreich, джерело