OOP vs Inline mit Arduino

8

Ich programmiere schon eine ganze Weile, aber ich bin neu in der Arduino- und AVR-Programmierung. Die Hauptfrage, die ich zur Programmierung dieser Mikrocontroller habe, ist, ob es große Unterschiede beim Entwerfen von Code in objektorientierten Klassen gegenüber der traditionelleren Inline-Programmierung gibt, die ich in vielen Beispielen gesehen habe.

Mit anderen Worten, gibt es in der Welt der Arduino / AVR-Controller Einsparungen bei Speicher und Leistung bei Verwendung von Klassen oder umgekehrt?

Nehmen wir zum Beispiel an, wir haben eine Klasse:

class SomeClass(){

private:
   int x;
   int y;

public:
   void foo();
   void bar();
}

SomeClass thisClass;
thisClass.foo();
thisClass.bar();

Würde es irgendwelche Leistungs- oder Speichergewinne geben, die das Programm inlineer gestalten, wie:

int x;
int y;

void foo(){ /*** Do something ***/};
void bar(){ /*** Do more stuff ***/};

Ich habe versucht, einige Suchanfragen in Stack Exchange und Google durchzuführen, konnte jedoch nicht die Antwort finden, die ich suche. Das nächste, was ich finden konnte, war diese Stack Exchange-Frage.

Der Grund, warum ich danach frage, ist, dass ich ein Projekt habe, das so leicht wie möglich sein muss, und mir ist nicht klar, wie ich mein Programm in dieser Umgebung gestalten soll.


Bearbeiten

Vielen Dank für die Antworten, dies hat Licht ins Dunkel gebracht. Eines ist mir nicht ganz klar.

Angenommen, Sie haben eine Klasse, die Sie entwerfen und die u8glib wie folgt verwendet:

class UserInterface{
private:
   U8GLIB_ST7920_128X64 Display;

public:
   UserInterface();
}

Wie würden Sie sich mit "Dynamic Memory" wie folgt fortbewegen?

UserInterface::UserInterface(){
   UserInterface::Display = U8GLIB_ST7920_128X64(LCD_E_PIN, LCD_RW_PIN, LCD_RS_PIN, U8G_PIN_NONE);
}
Andy Braham
quelle

Antworten:

2

Die Hauptfrage, die ich zur Programmierung dieser Mikrocontroller habe, ist, ob es große Unterschiede beim Entwerfen von Code in objektorientierten Klassen gegenüber der traditionelleren Inline-Programmierung gibt, die ich in vielen Beispielen gesehen habe.

...

Mit anderen Worten, gibt es in der Welt der Arduino / AVR-Controller Einsparungen bei Speicher und Leistung bei Verwendung von Klassen oder umgekehrt?

Ja, es gibt einen großen Unterschied zwischen der Verwendung von C oder C ++ für kleine eingebettete Systeme wie Arduino / AVR. In C ++ können weitere Informationen für Compileroptimierungen bereitgestellt werden.

Wenn Sie ein OOP-Framework implementieren, können Plattform- oder Laufzeit-C ++ und -Klassen auch bei der Softwarearchitektur und -wiederverwendung helfen. In Cosa werden eine Reihe von OOP-Entwurfsmustern verwendet, um Schnittstellen sowohl für Anwendungsprogrammierer als auch für Gerätetreiberprogrammierer zu erreichen. Am häufigsten ist die Delegation .

Durch die Verwendung abstrakter Klassen, virtueller Elementfunktionen, Inlining und Vorlagen können Sie einen geringeren Platzbedarf und eine höhere Leistung als bei herkömmlichen Implementierungen erzielen. Beispielsweise sind die Cosa Pin-Klassen X5-X10 schneller als der Arduino-Kern und gleichzeitig kleiner. Bitte beachten Sie die Benchmarks .

Eine Sache, die Sie von der traditionellen C ++ - Programmierung "verlernen" sollten, ist die Verwendung von new / delete (malloc / free). Bei einer SRAM-Größe von nur wenigen KByte ist die Verwendung eines Heaps ein großes Risiko. Die Antwort sind statische Klassen und stapelbasierte Daten.

Es gibt noch viel mehr über die OOP-Framework-Architektur zu sagen, aber ich hoffe, dies hilft bei der Beantwortung Ihrer ersten Fragen.

Prost!

Mikael Patel
quelle
Gute Antwort! Ich habe meine Frage aktualisiert und gefragt, wie ich mich im dynamischen Speicher bewegen soll (new / delete / malloc / free). Haben Sie Eingaben dazu, die dynamische Speicherzuweisung nicht zu verwenden? Sollte alles, was in den Klassen geteilt werden muss, global sein? Das klingt für mich nicht richtig, mir wurde immer beigebracht, keine Globals zu verwenden, wenn Sie helfen können.
Andy Braham
Ein kurzer Kommentar zu Ihrem obigen UserInterface-Beispiel. Die Anzeige ist eigentlich eine Objektzusammensetzung (keine Referenz / kein Zeiger), sodass Sie keine neuen benötigen. Sie müssen die Anzeige starten. Die UserInterface-Konstruktion sollte ungefähr so ​​aussehen. UserInterface::UserInterface() : Display(LCD_E_PIN, LCD_RW_PIN, LCD_RS_PIN, U8G_PIN_NONE) { ... }. Die erforderlichen Anzeigekonstruktorparameter sollten an den UserInterface-Konstruktor übergeben werden.
Mikael Patel
Bei OOP dreht sich alles um die Kapselung und das Ausblenden von Daten. Daher sollte die Freigabe ein Minimum (Null) sein. Das Ziel ist, mehr oder weniger nur eine Reihe globaler statischer Objekte zu haben, die interagieren. Sie halten und verstecken den globalen Staat. Für die Objekte sind die Mitgliedsdaten ein nicht dynamischer lokaler Status. Um das Ziel zu erreichen, benötigen Sie erneut eine Reihe von Tricks. Programmtransformationen.
Mikael Patel
Ein Beispiel; Betrachten Sie das Design einer BitSet-Klasse mit einer variablen Anzahl von Mitgliedern. Die naheliegende Lösung besteht darin, die Anzahl der erforderlichen Bytes zu berechnen und new / malloc zu verwenden. Eine Alternative besteht darin, den Speicher als Parameter an den BitSet-Konstruktor zu übergeben. Eine dritte Alternative ist eine Vorlagenklasse mit der Anzahl der Elemente als Parameter. Dies ermöglicht eine Mitgliedsvariable, die die erforderliche Anzahl von Bytes ist. Weitere Informationen zu dieser Variante der Programmtransformation finden Sie in Cosa / BitSet.hh . Es gibt mehr Transformationen.
Mikael Patel
Ich habe mein UserInterface-Beispiel aktualisiert. Ist das mehr oder weniger der richtige Ansatz? Ich denke, ich habe jetzt ein sehr gutes Verständnis dafür, wie ich das umsetzen kann, was ich brauche.
Andy Braham
4

Der Grund, warum Sie die Antwort nicht finden können, ist, dass die Antwort sowohl Ja als auch Nein ist.

Für grundlegende Klassenaufgaben - Definieren Ihrer Klasse mit Methoden usw. und Instanziieren von Objekten daraus - gibt es kaum einen Unterschied im Endergebnis im Vergleich zu "Vanille" C. Die Optimierungen des Compilers sind jetzt so gut, dass die Leistung genau gleich ist. Ja, es kann ein leichter Anstieg der Speichernutzung, da Sie einen zusätzlichen Zeiger mit jedem Methodenaufruf vorbei sind (statt foo(int x)Sie haben foo(MyClass *this, int x)) , aber das ist so klein wie nicht wahrnehmbar.

Die großen Unterschiede ergeben sich, wenn Sie anfangen, mit Polymorphismus und anderen fortgeschrittenen Themen zu spielen. Wenn der Compiler mit diesen komplexen Programmen beginnt, kann er nicht immer herausfinden, welche Funktionen erforderlich sind und welche nicht, und er kann die nicht verwendeten Funktionen nicht ausschneiden ( "Garbage Collection" ). Möglicherweise erhalten Sie also einen größeren Code.

Das bedeutet nicht, dass der Code langsamer ist, sondern nur herumhängende Codestücke, die niemals etwas bewirken.

Von größerer Bedeutung ist die bessere Verwaltung Ihres dynamischen Speichers als Sie es gewohnt sind. Da so wenig Speicher vorhanden ist, ist der Heap sehr klein und wird daher sehr leicht fragmentiert. Dynamische Erzeugung und Zerstörung von Objekten ( new myClass, delete myClassObjectusw.) ist sehr schlecht. Klassenobjekte müssen entweder statisch definiert sein (im globalen Bereich am häufigsten) oder vorübergehend auf dem Stapel zugewiesen werden (lokale Instanzen). Andernfalls bitten Sie um Ärger - und als erstes werden Sie feststellen, dass seltsame Dinge passieren (keine Fehlerberichte oder Ausnahmen, sehen Sie ...).

Majenko
quelle
Okay, wenn ich das in dieser Welt der Programmierung richtig verstehe, werden Klassen eher für eine organisatorische und Portabilitätsrolle als für echte OOP verwendet und die Dinge sollten eher statisch und explizit als dynamisch zugewiesen werden. Vielen Dank, das macht sehr viel Sinn und erklärt, warum ich Beispiele gesehen habe und was nicht so geschrieben wurde, wie sie sind.
Andy Braham