Sind globale Variablen in Arduino böse?

24

Ich bin relativ neu in der Programmierung und viele der Codierungsempfehlungen, die ich effektiv lese, besagen, dass es nur sehr wenige gute Gründe gibt, eine globale Variable zu verwenden (oder dass der beste Code überhaupt keine globalen Elemente enthält).

Ich habe mein Bestes getan, um dies zu berücksichtigen, wenn ich Software schreibe, um eine Arduino-Schnittstelle mit einer SD-Karte zu erstellen, mit einem Computer zu sprechen und einen Motorcontroller zu betreiben.

Ich habe derzeit 46 Globals für ungefähr 1100 Zeilen "Anfänger" -Code (keine Zeile hat mehr als eine Aktion). Ist das ein gutes Verhältnis oder sollte ich es mehr reduzieren? Welche Methoden kann ich auch anwenden, um die Anzahl der Globalen weiter zu reduzieren?

Ich frage dies hier, weil ich mich speziell mit Best Practices für das Codieren von Arduino-Produkten befasse, und nicht mit Computerprogrammierung im Allgemeinen.

ATE-ENGE
quelle
2
In Arduino können Sie globale Variablen nicht vermeiden. Jede Variablendeklaration außerhalb des Gültigkeitsbereichs einer Funktion / Methode ist global. Wenn Sie also Werte zwischen Funktionen teilen müssen, müssen diese global sein, es sei denn, Sie möchten jeden Wert als Argument übergeben.
16
@LookAlterno Err, kannst du keine Klassen in Ardunio schreiben, da es nur C ++ mit seltsamen Makros und Bibliotheken ist? Wenn ja, ist nicht jede Variable global. Und selbst in C wird es normalerweise als bewährte Methode angesehen, Variablen (möglicherweise innerhalb von Strukturen) an Funktionen zu übergeben, anstatt globale Variablen zu haben. Könnte für ein kleines Programm weniger praktisch sein, aber es lohnt sich normalerweise, wenn das Programm größer und komplexer wird.
Muzer
11
@LookAlterno: "Ich vermeide" und "Du kannst nicht" sind sehr unterschiedliche Dinge.
Leichtigkeit Rennen mit Monica
2
Einige eingebettete Programmierer verbieten lokale Variablen und benötigen stattdessen globale Variablen (oder funktionsbezogene statische Variablen). Wenn die Programme klein sind, kann es einfacher sein, sie als einfache Zustandsmaschine zu analysieren. weil Variablen sich nie gegenseitig überschreiben (dh wie sie zugewiesene Variablen stapeln und häufen).
Rob
1
Statische und globale Variablen bieten den Vorteil, den Speicherverbrauch während der Kompilierung zu kennen. Bei einem Arduino mit sehr begrenztem verfügbaren Speicher kann dies ein Vorteil sein. Für einen Anfänger ist es ziemlich einfach, den verfügbaren Speicher zu erschöpfen und unauffindbare Fehler zu erleben.
Antimuster

Antworten:

33

Sie sind an sich nicht böse , aber sie neigen dazu, überbeansprucht zu werden, wenn es keinen guten Grund gibt, sie zu benutzen.

Es gibt viele Male, in denen globale Variablen von Vorteil sind. Zumal sich die Programmierung eines Arduino unter der Haube stark von der Programmierung eines PCs unterscheidet.

Der größte Vorteil globaler Variablen ist die statische Zuordnung. Besonders bei großen und komplexen Variablen wie Klasseninstanzen. Die dynamische Zuweisung (die Verwendung von newusw.) wird aufgrund fehlender Ressourcen verpönt.

Außerdem erhalten Sie nicht wie in einem normalen C-Programm einen einzelnen Aufrufbaum (einzelne main()Funktion, die andere Funktionen aufruft), sondern zwei separate Bäume ( setup()Funktionen aufrufen, dann loop()Funktionen aufrufen), was bedeutet, dass manchmal globale Variablen die Variablen sind Nur so können Sie Ihr Ziel erreichen (dh wenn Sie es in beiden setup()und verwenden möchten loop()).

Also nein, sie sind nicht böse und auf einem Arduino haben sie mehr und bessere Verwendungsmöglichkeiten als auf einem PC.

Majenko
quelle
Ok, was ist, wenn es etwas ist, das ich nur in loop()(oder in mehreren aufgerufenen Funktionen loop()) benutze ? Wäre es besser, sie anders einzurichten als zu Beginn zu definieren?
ATE-ENGE
1
In diesem Fall würde ich sie wahrscheinlich in loop () definieren (vielleicht als staticob ich sie brauche, um ihren Wert über Iterationen hinweg beizubehalten) und sie durch die Funktionsparameter in der gesamten Aufrufkette weiterleiten.
Majenko
2
Das ist eine Möglichkeit, oder geben Sie es als Referenz: void foo(int &var) { var = 4; }und foo(n);- nist jetzt 4.
Majenko
1
Klassen können gestapelt werden. Zugegeben, es ist nicht gut, große Instanzen auf den Stapel zu legen, aber dennoch.
JAB
1
@JAB Jeder kann einem Stapel zugewiesen werden.
Majenko
18

Es ist sehr schwierig, eine endgültige Antwort zu geben, ohne Ihren tatsächlichen Code zu sehen.

Globale Variablen sind nicht böse und in einer eingebetteten Umgebung, in der Sie normalerweise häufig auf Hardware zugreifen, oft sinnvoll. Sie haben nur vier UARTS, nur einen I2C-Port usw. Daher ist es sinnvoll, Globals für Variablen zu verwenden, die an bestimmte Hardwareressourcen gebunden sind. Und in der Tat, die Arduino - Core - Bibliothek tut das: Serial, Serial1usw. sind globale Variablen. Außerdem ist eine Variable, die den globalen Status des Programms darstellt, in der Regel global.

Ich habe derzeit 46 Globals für etwa 1100 Zeilen [Code]. Ist das ein gutes Verhältnis [...]

Geht nicht um die Zahlen. Die richtige Frage, die Sie sich stellen sollten, ist für jeden dieser Globalen, ob es Sinn macht, ihn im globalen Rahmen zu haben.

Trotzdem scheint mir 46 global ein bisschen hoch zu sein. Wenn einige dieser Werte konstant bleiben, qualifizieren Sie sie als const: Der Compiler optimiert normalerweise ihren Speicherplatz. Wenn eine dieser Variablen nur in einer einzelnen Funktion verwendet wird, machen Sie sie lokal. Wenn der Wert zwischen den Aufrufen der Funktion bestehen bleiben soll, kennzeichnen Sie diese als static. Sie können auch die Anzahl der "sichtbaren" Globals reduzieren, indem Sie Variablen innerhalb einer Klasse gruppieren und eine globale Instanz dieser Klasse haben. Aber nur dann, wenn es Sinn macht, Dinge zusammenzustellen. Wenn Sie eine große GlobalStuffKlasse erstellen, um nur eine globale Variable zu haben, wird Ihr Code nicht klarer.

Edgar Bonet
quelle
1
Okay! Ich wusste nicht, staticob ich eine Variable habe, die nur in einer Funktion verwendet wird, aber jedes Mal, wenn eine Funktion aufgerufen wird (wie var=millis()) , einen neuen Wert erhalte, sollte ich das machen static?
ATE-ENGE
3
Nr. staticIst nur für den Fall, dass Sie den Wert beibehalten müssen. Wenn Sie als Erstes den Wert der Variablen auf die aktuelle Zeit setzen, müssen Sie den alten Wert nicht beibehalten. In dieser Situation verschwendet die statische Aufladung nur Speicher.
Andrew
Dies ist eine sehr runde Antwort, die sowohl Punkte zugunsten als auch Skepsis gegenüber Globalen zum Ausdruck bringt. +1
underscore_d
6

Das Hauptproblem bei globalen Variablen ist die Codeverwaltung. Beim Lesen einer Codezeile ist es einfach, die Deklaration von Variablen zu finden, die als Parameter übergeben oder lokal deklariert wurden. Es ist nicht so einfach, die Deklaration globaler Variablen zu finden (oft ist eine IDE erforderlich).

Wenn Sie viele globale Variablen haben (40 ist bereits eine Menge), wird es schwierig, einen expliziten Namen zu haben, der nicht zu lang ist. Mit dem Namespace können Sie die Rolle globaler Variablen verdeutlichen.

Ein schlechter Weg, Namespaces in C nachzuahmen, ist:

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

Auf Intel- oder Arm-Prozessoren ist der Zugriff auf globale Variablen langsamer als auf andere Variablen. Auf Arduino ist es wahrscheinlich das Gegenteil.

BOC
quelle
2
Bei AVR befinden sich Globals im RAM, wohingegen die meisten nicht statischen Locals den CPU-Registern zugeordnet sind, was den Zugriff beschleunigt.
Edgar Bonet
1
Das Problem besteht darin, die Deklaration nicht wirklich zu finden. in der Lage , den Code schnell zu verstehen , ist wichtig , aber letztlich sekundär , was sie tut - und es ist das eigentliche Problem Erkenntnis , die varmint , in der unbekannten Teil des Codes ist in der Lage zu verwenden die globale Deklaration Sie seltsame Dinge mit einem Objekt zu tun ausgelassen im Freien, damit jeder machen kann, was er will.
Underscore_d
5

Obwohl ich sie beim Programmieren für einen PC nicht verwenden würde, haben sie für das Arduino einige Vorteile. Am meisten, wenn es schon gesagt wurde:

  • Keine dynamische Speichernutzung (Erstellen von Lücken im begrenzten Heap-Speicher eines Arduino)
  • Überall verfügbar, daher müssen sie nicht als Argumente übergeben werden (was Stapelplatz kostet)

In einigen Fällen, insbesondere in Bezug auf die Leistung, kann es auch sinnvoll sein, globale Variablen zu verwenden, anstatt bei Bedarf Elemente zu erstellen:

  • Verringerung der Lücken im Heap-Bereich
  • Das dynamische Zuweisen und / oder Freigeben von Speicher kann viel Zeit in Anspruch nehmen
  • Variablen können wie eine Liste von Elementen einer globalen Liste wiederverwendet werden, die aus mehreren Gründen verwendet werden, z. B. einmal als Puffer für die SD und später als temporärer Zeichenfolgenpuffer.
Michel Keijzers
quelle
5

Wie bei allem (außer Gotos, die wirklich böse sind) haben Globale ihren Platz.

Beispiel: Wenn Sie eine serielle Debug-Schnittstelle oder eine Protokolldatei haben, in die Sie von überall aus schreiben können müssen, ist es oft sinnvoll, sie global zu gestalten. Wenn Sie wichtige Systemstatusinformationen haben, ist es häufig die einfachste Lösung, diese global zu machen. Es hat keinen Sinn, einen Wert zu haben, den Sie an jede einzelne Funktion im Programm übergeben müssen.

Wie andere gesagt haben, scheint 46 für nur etwas mehr als 1000 Codezeilen eine Menge zu sein, aber ohne die Details zu wissen, was Sie tun, ist es schwer zu sagen, ob Sie sie überflüssig machen oder nicht.

Stellen Sie sich jedoch für jeden Globus ein paar wichtige Fragen:

Ist der Name klar und eindeutig, damit ich nicht versehentlich versuche, den gleichen Namen an einer anderen Stelle zu verwenden? Wenn nicht, ändern Sie den Namen.

Muss sich das jemals ändern? Wenn nicht, machen Sie es zu einer Konstante.

Muss dies überall sichtbar sein oder ist es nur global, damit der Wert zwischen den Funktionsaufrufen erhalten bleibt? Erwägen Sie daher, es für die Funktion lokal zu machen und das Schlüsselwort static zu verwenden.

Wird es wirklich schlimm werden, wenn sich das durch einen Code ändert, wenn ich nicht aufpasse? Wenn Sie z. B. zwei zusammengehörige Variablen haben, z. B. Name und ID-Nummer, die global sind (siehe vorherigen Hinweis zur Verwendung von global, wenn Sie Informationen fast überall benötigen), kann eine davon geändert werden, ohne dass die anderen bösen Dinge passieren. Ja, Sie könnten vorsichtig sein, aber manchmal ist es gut, ein wenig Vorsicht walten zu lassen. Legen Sie sie z. B. in eine andere .c-Datei und definieren Sie dann Funktionen, mit denen Sie beide gleichzeitig festlegen und sie lesen können. Sie fügen dann nur die Funktionen in die zugehörige Header-Datei ein. Auf diese Weise kann der Rest Ihres Codes nur über die definierten Funktionen auf die Variablen zugreifen und so nichts durcheinander bringen.

- update - Ich habe gerade festgestellt, dass Sie eher nach Arduino-spezifischen Best Practices als nach allgemeiner Codierung gefragt haben, und dies ist eher eine allgemeine Codierungsantwort. Aber ehrlich gesagt gibt es keinen großen Unterschied, gute Praxis ist gute Praxis. Die startup()und loop()Struktur von Arduino bedeutet, dass Sie in einigen Situationen ein wenig mehr Globals verwenden müssen als andere Plattformen, aber das ändert nicht wirklich viel. Am Ende streben Sie immer das Beste an, was Sie innerhalb der Grenzen der Plattform tun können, egal was passiert die plattform ist.

Andrew
quelle
Ich weiß nichts über Arduinos, aber ich mache viel Desktop- und Serverentwicklung. Es gibt eine akzeptable Verwendung (IMHO) für gotos, die darin besteht, aus verschachtelten Schleifen auszubrechen. Sie ist viel sauberer und verständlicher als die Alternativen.
Ausdauer
Wenn Sie der Meinung gotosind, dass es böse ist, lesen Sie den Linux-Code. Sie sind wohl genauso böse wie try...catchBlöcke, wenn sie als solche verwendet werden.
Dmitry Grigoryev
5

Sind sie böse Vielleicht. Das Problem bei Globals ist, dass sie zu jedem Zeitpunkt von einer Funktion oder einem Codeteil, der ausgeführt wird, ohne Einschränkungen aufgerufen und geändert werden können. Dies kann zu Situationen führen, die sich beispielsweise nur schwer zurückverfolgen und erklären lassen. Es ist daher wünschenswert, die Anzahl der Globalen zu minimieren, wenn möglich, um die Anzahl auf Null zurückzusetzen.

Können sie vermieden werden? Fast immer ja Das Problem mit Arduino ist, dass sie Sie zu diesem Ansatz mit zwei Funktionen zwingen, bei dem sie Sie setup()und Sie übernehmen loop(). In diesem speziellen Fall haben Sie (wahrscheinlich main()) keinen Zugriff auf den Umfang der Anruferfunktion dieser beiden Funktionen . Wenn Sie dies getan hätten, könnten Sie sich von allen Globalen befreien und stattdessen Einheimische verwenden.

Stellen Sie sich Folgendes vor:

int main() {
  setup();

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

Dies ist wahrscheinlich mehr oder weniger die Hauptfunktion eines Arduino-Programms. Variablen, die Sie sowohl in der setup()als auch in der loop()Funktion benötigen, werden dann vorzugsweise im Geltungsbereich der deklariertmain() Funktionsbereichs und nicht des globalen Bereichs . Sie könnten dann den beiden anderen Funktionen zugänglich gemacht werden, indem sie als Argumente übergeben werden (ggf. unter Verwendung von Zeigern).

Beispielsweise:

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

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

Beachten Sie, dass Sie in diesem Fall auch die Signatur beider Funktionen ändern müssen.

Da dies möglicherweise weder machbar noch wünschenswert ist, sehe ich wirklich nur einen Weg, um die meisten globalen Elemente aus einem Arduino-Programm zu entfernen, ohne die erzwungene Programmstruktur zu ändern.

Wenn ich mich richtig erinnere, können Sie C ++ perfekt verwenden, während Sie für Arduino programmieren, anstatt C. Wenn Sie (noch) nicht mit OOP (Object Oriented Programming) vertraut sind oder C ++ , ist es möglicherweise gewöhnungsbedürftig lesen.

Mein Vorschlag wäre, eine Program-Klasse und eine einzelne globale Instanz dieser Klasse zu erstellen. Eine Klasse sollte als Blaupause für Objekte betrachtet werden.

Betrachten Sie das folgende Beispielprogramm:

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();
}

Voilà, wir haben uns von fast allen Globalen befreit. Die Funktionen, in denen Sie beginnen würden, Ihre Anwendungslogik hinzuzufügen, wären die Funktionen Program::setup()und Program::loop(). Diese Funktionen haben Zugriff auf die Instanz bestimmten Elementvariablen myFirstSampleVariableund mySecondSampleVariablewährend der traditionellen setup()und loop()Funktionen haben keinen Zugriff , da diese Variablen Klasse privat markiert hat. Dieses Konzept wird als Datenkapselung oder Verstecken von Daten bezeichnet.

Das Unterrichten von OOP und / oder C ++ ist in der Beantwortung dieser Frage nicht ausreichend, daher höre ich hier auf.

Zusammenfassend lässt sich sagen, dass Globals vermieden werden sollten und es fast immer möglich ist, die Anzahl der Globals drastisch zu reduzieren. Auch wenn Sie für Arduino programmieren.

Vor allem hoffe ich, dass meine Antwort für Sie nützlich ist :)

Arjen
quelle
Sie können Ihr eigenes main () in der Skizze definieren, wenn Sie es vorziehen. So sieht die Aktie aus: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234
Ein Singleton ist im Grunde genommen ein globaler Typ, der sich nur auf verwirrende Weise verkleidet. Es hat die gleichen Nachteile.
Patstew
@patstew Hast du etwas dagegen, mir zu erklären, wie du das Gefühl hast, dass es die gleichen Nachteile hat? Meiner Meinung nach ist dies nicht der Fall, da Sie die Datenkapselung zu Ihrem Vorteil verwenden können.
Arjen
@ per1234 Danke! Ich bin definitiv kein Arduino-Experte, aber ich nehme an, mein erster Vorschlag könnte auch dann funktionieren.
Arjen
2
Nun, es ist immer noch ein globaler Status, auf den überall im Programm zugegriffen werden kann. Program::instance().setup()Stattdessen greifen Sie einfach über zu globalProgram.setup(). Das Einfügen verwandter globaler Variablen in eine Klasse / Struktur / einen Namespace kann von Vorteil sein, insbesondere wenn sie nur von einigen verwandten Funktionen benötigt werden, das Singleton-Muster jedoch nichts hinzufügt. Mit anderen Worten, static Program p;verfügt über globalen Speicher und static Program& instance()hat globalen Zugriff, was genauso viel wie einfach ist Program globalProgram;.
Patstew
4

Globale Variablen sind niemals böse . Eine allgemeine Regel gegen sie ist nur eine Krücke, mit der Sie lange genug überleben können, um Erfahrungen zu sammeln und bessere Entscheidungen zu treffen.

Was eine globale Variable ist, ist eine inhärente Annahme, dass es nur eine einzige Sache gibt (es spielt keine Rolle, ob es sich um ein globales Array oder eine Map handelt, die möglicherweise mehrere Dinge enthält, die aber immer noch die Annahme enthalten, dass es nur eine gibt) eine solche Liste oder Zuordnung und nicht mehrere unabhängige).

Bevor Sie ein globales System nutzen, möchten Sie sich fragen: Ist es denkbar, dass ich jemals mehr als eines davon verwenden möchte? Wenn es sich später als wahr herausstellt, müssen Sie den Code ändern, um die Globalisierung des Objekts aufzuheben, und Sie werden wahrscheinlich feststellen, dass andere Teile Ihres Codes von dieser Annahme der Eindeutigkeit abhängen Ich muss sie auch reparieren, und der Prozess wird mühsam und fehleranfällig. "Verwenden Sie keine Globals" wird gelehrt, weil es normalerweise recht wenig kostet, Globals von Anfang an zu vermeiden, und es vermeidet das Potenzial, später große Kosten bezahlen zu müssen.

Aber die vereinfachenden Annahmen, die Globals erlauben, machen Ihren Code auch kleiner, schneller und verbrauchen weniger Speicher, weil er nicht um eine Vorstellung herumgeben muss, welche Sache er verwendet, nicht umleiten muss, nicht umleiten muss Berücksichtigen Sie die Möglichkeit, dass das gewünschte Objekt möglicherweise nicht vorhanden ist usw. Bei eingebetteten Systemen ist die Wahrscheinlichkeit höher, dass die Codegröße und / oder die CPU-Zeit und / oder der Arbeitsspeicher eingeschränkt sind, als bei einem PC. Daher können diese Einsparungen von Bedeutung sein. Und viele eingebettete Anwendungen haben auch höhere Anforderungen - Sie wissen, dass Ihr Chip nur ein bestimmtes Peripheriegerät hat, der Benutzer kann nicht einfach ein anderes an einen USB-Anschluss anschließen oder so.

Ein weiterer häufiger Grund für den Wunsch nach mehr als einer Funktion, die eindeutig zu sein scheint, ist das Testen - das Testen der Interaktion zwischen zwei Komponenten ist einfacher, wenn Sie lediglich eine Testinstanz einer Komponente an eine Funktion übergeben können, während das Ändern des Verhaltens einer globalen Komponente der Grund ist eine kniffligere Angelegenheit. Das Testen in der Embedded-Welt unterscheidet sich jedoch in der Regel erheblich von anderen Anbietern. Daher trifft dies möglicherweise nicht auf Sie zu. Soweit ich weiß, hat Arduino überhaupt keine Testkultur.

Setzen Sie also Globals ein, wenn es sich zu lohnen scheint. Die Codepolizei wird Sie nicht abholen. Wisse nur, dass die falsche Wahl später zu viel mehr Arbeit für dich führen könnte. Wenn du dir also nicht sicher bist ...

hobbs
quelle
0

Sind globale Variablen in Arduino böse?

Nichts ist von Natur aus böse, einschließlich globaler Variablen. Ich würde es als "notwendiges Übel" bezeichnen - es kann Ihr Leben viel einfacher machen, aber es sollte mit Vorsicht angegangen werden.

Welche Methoden kann ich auch anwenden, um die Anzahl der Globalen weiter zu reduzieren?

Verwenden Sie Wrapper-Funktionen, um auf Ihre globalen Variablen zuzugreifen. Sie verwalten es also zumindest aus der Scope-Perspektive.

dannyf
quelle
3
Wenn Sie Wrapper-Funktionen für den Zugriff auf globale Variablen verwenden, können Sie Ihre Variablen auch in diese Funktionen einfügen.
Dmitry Grigoryev