Являются ли глобальные переменные злыми в Arduino?

24

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

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

В настоящее время у меня есть 46 глобальных переменных для примерно 1100 строк кода «начального уровня» (ни одна строка не содержит более одного действия). Это хорошее соотношение или мне стоит больше его уменьшать? Кроме того, какие методы я могу использовать, чтобы еще больше сократить количество глобалов?

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

ATE-Энж
источник
2
В Arduino вы не можете избежать глобальных переменных. Каждое объявление переменной вне области функции / метода является глобальным. Таким образом, если вам нужно разделить значения между функциями, они должны быть глобальными, если только вы не хотите передавать каждое значение в качестве аргумента.
16
@LookAlterno Err, вы не можете писать классы в Ardunio, так как это просто C ++ со странными макросами и библиотеками? Если это так, не каждая переменная является глобальной. И даже в C обычно считается предпочтительным передавать переменные (возможно, внутри структур) в функции, а не иметь глобальные переменные. Может быть менее удобно для небольшой программы, но обычно окупается, когда программа становится больше и сложнее.
Музер
11
@LookAlterno: «Я избегаю» и «ты не можешь» - это очень разные вещи.
Легкость гонок с Моникой
2
На самом деле, некоторые встроенные программисты запрещают локальные переменные и вместо них требуют глобальные переменные (или статические переменные в области действия функции). Когда программы маленькие, это может облегчить их анализ в виде простого конечного автомата; потому что переменные никогда не перезаписывают друг друга (т. е. они выделяются и складывают переменные в куче).
Роб
1
Статические и глобальные переменные дают преимущество в знании потребления памяти во время компиляции. С Arduino, имеющим очень ограниченную доступную память, это может быть преимуществом. Для новичка довольно легко исчерпать доступную память и испытать неотслеживаемые сбои.
антипаттерн

Ответы:

33

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

Есть много раз, когда глобальные переменные являются выгодными. Тем более, что программирование Arduino под капотом сильно отличается от программирования на ПК.

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

Кроме того, вы не получаете одно дерево вызовов, как в обычной программе на Си (одна main()функция, вызывающая другие функции) - вместо этого вы фактически получаете два отдельных дерева ( setup()вызывая функции, затем loop()вызывая функции), что означает, что иногда глобальные переменные являются Единственный способ достичь своей цели (т. е. если вы хотите использовать ее в обоих setup()и loop()).

Так что нет, они не злые, и на Arduino они используются лучше и лучше, чем на ПК.

Маженко
источник
Хорошо, что если это то, что я использую только в loop()(или в нескольких вызванных функциях loop())? было бы лучше настроить их иначе, чем определять их в начале?
ATE-ENGE
1
В этом случае я, вероятно, определил бы их в loop () (возможно, как staticесли бы я нуждался в них, чтобы они сохраняли свое значение на протяжении итераций) и передавал их через параметры функции по цепочке вызовов.
Маженко
2
Это один из способов, или передать его в качестве ссылки: void foo(int &var) { var = 4; }и foo(n);- nтеперь 4.
Majenko
1
Классы могут быть размещены в стеке. Следует признать, что помещать большие экземпляры в стек нехорошо, но все же.
JAB
1
@JAB Все может быть размещено в стеке.
Маженко
18

Очень сложно дать однозначный ответ, не увидев ваш реальный код.

Глобальные переменные не являются злом, и они часто имеют смысл во встроенной среде, где вы обычно имеете большой аппаратный доступ. У вас есть только четыре UARTS, только один порт I2C и т. Д. Поэтому имеет смысл использовать глобальные переменные для переменных, привязанных к конкретным аппаратным ресурсам. И действительно, Arduino библиотека ядра делает это: Serial, Serial1и т.д. являются глобальными переменными. Кроме того, переменная, представляющая глобальное состояние программы, обычно является глобальной.

В настоящее время у меня есть 46 глобальных переменных для примерно 1100 строк [кода]. Это хорошее соотношение [...]

Не о цифрах. Правильный вопрос, который вы должны себе задать, заключается в том, имеет ли смысл иметь его в глобальном масштабе для каждого из этих глобалов.

Тем не менее, 46 глобальных кажется мне немного выше. Если некоторые из них содержат постоянные значения, квалифицируйте их как const: компилятор обычно оптимизирует их хранилище. Если какая-либо из этих переменных используется только внутри одной функции, сделайте ее локальной. Если вы хотите, чтобы его значение сохранялось между вызовами функции, определите его как static. Вы также можете уменьшить количество «видимых» глобальных переменных, сгруппировав переменные внутри класса и имея один глобальный экземпляр этого класса. Но делайте это только тогда, когда есть смысл собрать вещи вместе. Создание большого GlobalStuffкласса ради единственной глобальной переменной не поможет сделать ваш код более понятным.

Эдгар Бонет
источник
1
Хорошо! Я не знал, есть staticли у меня переменная, которая используется только в одной функции, но я получаю новое значение каждый раз, когда вызывается функция (как var=millis()), должен ли я сделать это static?
ATE-ENGE
3
staticтолько для того, когда вам нужно сохранить значение. Если первое, что вы делаете в функции, это устанавливаете значение переменной в текущее время, то нет необходимости сохранять старое значение. Все, что делает статика в этой ситуации, тратит память.
Андрей
Это очень всесторонний ответ, выражающий обе точки зрения в пользу глобалов и их скептицизм. +1
underscore_d
6

Основной проблемой с глобальными переменными является обслуживание кода. При чтении строки кода легко найти объявление переменных, переданных в качестве параметра или объявленных локально. Не так просто найти объявление глобальных переменных (часто для этого требуется и IDE).

Когда у вас много глобальных переменных (40 уже много), становится трудно иметь явное имя, которое не слишком длинное. Использование пространства имен - это способ прояснить роль глобальных переменных.

Плохой способ имитировать пространства имен в C:

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

На процессорах Intel или ARM доступ к глобальным переменным медленнее, чем к другим переменным. Это, вероятно, наоборот на Arduino.

BOC
источник
2
В AVR глобальные переменные находятся в оперативной памяти, тогда как большинство нестатических локальных ресурсов выделяются для регистров ЦП, что ускоряет их доступ.
Эдгар Бонет
1
Проблема не в том, чтобы найти объявление; возможность быстрого понимания кода важна, но в конечном итоге является вторичной по отношению к тому, что он делает, и здесь реальная проблема заключается в том, чтобы найти, какой varmint, в котором неизвестная часть вашего кода может использовать глобальное объявление, чтобы делать странные вещи с объектом, который вы пропустили в открытую для всех, чтобы делать то, что они хотят с.
underscore_d
5

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

  • Отсутствие динамического использования памяти (создание пробелов в ограниченном пространстве кучи Arduino)
  • Доступен везде, поэтому нет необходимости передавать их в качестве аргументов (что стоит места в стеке)

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

  • Уменьшить пробелы в куче
  • Динамическое выделение и / или освобождение памяти может занять значительное время
  • Переменные могут быть «использованы повторно», например, список элементов глобального списка, которые используются по нескольким причинам, например, один раз в качестве буфера для SD, а затем в качестве временного строкового буфера.
Мишель Кейзерс
источник
5

Как и во всем (кроме gotos, которые действительно злые) глобалы имеют свое место.

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

Как уже говорили другие 46, кажется, много всего для более чем 1000 строк кода, но, не зная деталей того, что вы делаете, трудно сказать, перешли ли вы на их использование или нет.

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

Является ли имя ясным и конкретным, чтобы я случайно не попытался использовать то же имя где-нибудь еще? Если нет, измените имя.

Должно ли это когда-либо меняться? Если нет, то подумайте о том, чтобы сделать его постоянным.

Должно ли это быть видимым везде или только глобально, чтобы значение поддерживалось между вызовами функций? Поэтому подумайте о том, чтобы сделать его локальным для функции и использовать ключевое слово static.

Неужели все будет плохо, если это будет изменено фрагментом кода, когда я не буду осторожен? например, если у вас есть две связанные переменные, скажем, имя и идентификационный номер, которые являются глобальными (см. предыдущее примечание об использовании global, когда вам нужна информация почти везде), то если одна из них будет изменена без других неприятных вещей, может произойти. Да, вы могли бы просто быть осторожными, но иногда полезно немного усилить осторожность. Например, поместите их в другой файл .c, а затем определите функции, которые устанавливают их одновременно и позволяют читать их. Затем вы только включаете функции в связанный заголовочный файл, таким образом, остальная часть вашего кода может получить доступ к переменным только через определенные функции и поэтому не может испортить ситуацию.

- update - я только что понял, что вы спрашивали о конкретной передовой практике 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. Если вы еще не знакомы (пока) с ООП (объектно-ориентированным программированием) или C ++, может потребоваться некоторое привыкание и некоторые чтение.

Моим предложением было бы создать класс Program и создать единый глобальный экземпляр этого класса. Класс должен рассматриваться как проект для объектов.

Рассмотрим следующий пример программы:

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() функции не имеют доступа , поскольку эти переменные были отмечены класса приватным. Эта концепция называется инкапсуляцией или сокрытием данных.

Обучение ООП и / или C ++ немного выходит за рамки ответа на этот вопрос, поэтому я на этом остановлюсь.

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

Самое главное, я надеюсь, что мой ответ несколько полезен для вас :)

Арьен
источник
Вы можете определить свой собственный main () в эскизе, если хотите. Вот как выглядит акция: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234
Синглтон, по сути, является глобальным, просто одетым в запутанную форму. У него такие же минусы.
Patstew
@patstew Не могли бы вы объяснить мне, как вы чувствуете, что у него такие же недостатки? На мой взгляд, это не так, поскольку вы можете использовать инкапсуляцию данных в своих интересах.
Арьен
@ per1234 Спасибо! Я определенно не эксперт Arduino, но я полагаю, что мое первое предложение могло бы сработать и тогда.
Арьен
2
Ну, это все еще глобальное состояние, к которому можно получить доступ в любом месте программы, вы просто получаете к нему доступ Program::instance().setup()вместо globalProgram.setup(). Помещение связанных глобальных переменных в одно пространство классов / структур / имен может быть полезным, особенно если они нужны только нескольким связанным функциям, но шаблон синглтона ничего не добавляет. Другими словами, static Program p;имеет глобальное хранилище и static Program& instance()глобальный доступ, что равносильно простому Program globalProgram;.
patstew
4

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

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

Поэтому, прежде чем использовать глобальный, вы хотите спросить себя: возможно ли, что я когда-нибудь захочу использовать больше, чем одну из этих вещей? Если это окажется правдой, вам придется изменить код, чтобы не глобализировать эту вещь, и вы, вероятно, обнаружите, что другие части вашего кода зависят от этого предположения уникальности, поэтому вы Придется исправлять и их, и процесс становится утомительным и подверженным ошибкам. Преподавание «Не используйте глобалы» объясняется тем, что обычно избегать глобальных затрат с самого начала довольно просто, и это исключает возможность того, что впоследствии придется заплатить большую цену.

Но упрощающие допущения, которые допускают глобальные переменные, также делают ваш код меньше, быстрее и используют меньше памяти, потому что он не должен передавать понятия о том, что он использует, не должен делать косвенные действия, не должен рассмотреть возможность того, что нужной вещи, возможно, не существует, и т. д. Во встроенной системе вы, скорее всего, будете ограничены размером кода и / или процессорного времени и / или памяти, чем на ПК, поэтому такая экономия может иметь значение. И многие встроенные приложения также имеют более жесткие требования - вы знаете, что у вашего чипа есть только одно периферийное устройство, пользователь не может просто подключить другое к USB-порту или к чему-то другому.

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

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

Hobbs
источник
0

Злые ли глобальные переменные в Arduino?

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

Кроме того, какие методы я могу использовать, чтобы еще больше сократить количество глобалов?

используйте функции-оболочки для доступа к вашим глобальным переменным. так что вы, по крайней мере, управляете этим с точки зрения области видимости.

dannyf
источник
3
Если вы используете функции-оболочки для доступа к глобальным переменным, вы можете также поместить свои переменные в эти функции.
Дмитрий Григорьев