Obwohl ich schon lange C / C ++ - Code geschrieben habe, haben mich die unsichtbaren Einschränkungen des Speicherbedarfs auf verschiedenen MCU- und SOC-Programmierplattformen oft gestolpert. Da ich im Begriff bin, Code für mein erstes großes Projekt für meine NANO-Boards zu erstellen, das wahrscheinlich einen angemessenen Teil seiner Ressourcen verbraucht, möchte ich besser auf überraschende und unerwartete "Fallstricke" vorbereitet sein .
Zum Beispiel war ich bei einem anderen SOC, an dem ich kürzlich ausgiebig gearbeitet habe (The Pololu.com wixel), sehr überrascht, diese Funktions- / Methodenargumente und automatischen Variablen zu erfahren, von denen ich normalerweise erwarten würde, dass sie stapelweise zugewiesen und wiederhergestellt werden, wenn eine Funktion zurückgegeben wird in der Tat dauerhaft für die Laufzeit des Programms zugewiesen! Beeindruckend! Auf dieser Plattform, auf der ich es normalerweise hasse, Variablen zu "überarbeiten" oder Variablen wiederzuverwenden, nachdem ihre Namen keinen Sinn mehr ergeben, musste ich meine Codierung an das anpassen, was ich aus Gründen der Lesbarkeit normalerweise für BAD halte. Ganz zu schweigen von der Erkenntnis, dass einfache Schleifenvariablen besser global zugeordnet werden sollten. Ja!
Können diejenigen von Ihnen, die in der Arduino-Umgebung auf solche unerwarteten Codierungsprobleme gestoßen sind, einige "spezielle" Richtlinien zu solchen Dingen teilen?
quelle
auto
in C ++ 11 von einem Speicherdauer-Bezeichner in einen Variablentyp-Bezeichner geändert. Der Einfachheit halber nicht verwendenauto
.Antworten:
Wie Sie selbst bemerkt haben, kann dies etwas plattformabhängig sein. Da Sie an einem Nano arbeiten, ist meine Antwort für die AVR-Architektur, wenn sie mit gcc, avr-libc und der Arduino-Kernbibliothek kompiliert wird.
Lassen Sie mich zunächst beruhigen: Dieses seltsame Verhalten, das Sie beim Wixel gesehen haben, tritt auf dieser Plattform nicht auf. Lokale Variablen werden meistens in CPU-Registern und dann auf dem Stapel zugewiesen.
Nun mag die erste allgemeine Regel offensichtlich klingen: Überlegen Sie zweimal, woran sich Ihr Programm wirklich erinnern muss. Verwenden Sie außerdem jedes Mal den kleinstmöglichen Datentyp. Die C99 - Typ
int8_t
,uint16_t
und co. sind dafür nützlich.Eine andere Regel, die Sie auf dieser Plattform häufig hören werden: Vermeiden Sie dynamische Zuweisungen, dh
malloc()
undnew
Sie können sich häufig keine Heap-Fragmentierung leisten. Vermeiden Sie auch dieString
Klasse, da sie auf dynamischer Zuordnung beruht. Bevorzugen Sie nach Möglichkeit die statische Zuordnung oder die Stapelzuweisung für kleine Dinge.Vergessen Sie nicht,
const
für Konstanten zu verwenden: Mit diesem Qualifikationsmerkmal kann der Compiler die Konstanten häufig als unmittelbare Operanden optimieren. Verwenden Sie für Arrays von KonstantenPROGMEM
. Ja, es ist nicht bequem zu bedienen, aber es kann Ihnen viel RAM sparen. DasF()
Makro zum Drucken konstanter Nachrichten ist ein Sonderfall vonPROGMEM
.Seien Sie zum Schluss sehr vorsichtig mit der Rekursion.
quelle
Edgar hat einige großartige Tipps, und ich werde einen wiederholen, bevor ich ein paar mehr gebe:
Vermeiden Sie dynamische Speicherzuweisungen
Verwenden Sie kein Malloc. Wenn Sie ein System entwerfen, budgetieren Sie Ihren RAM wie Ihr monatliches Budget. Stellen Sie sicher, dass Sie alles auf einmal erledigen können.
Und noch ein paar andere Tipps:
globale Statik
Nutzen Sie den BSS-Block voll aus. Versuchen Sie, den Speicherplatz in globalen
static
Arrays so weit wie möglich auszublenden. Sie mögen schaudern oder sich über die Idee lustig machen, alles auf eine globale Ebene zu stellen, aber eingebettet ist ein Paradigmenwechsel mit eigenen Regeln.Da Sie auf globaler Ebene so viele Variablen haben, können
static
Sie vermeiden, dass Ihr Namespace zu stark verschmutzt wird.Vermeiden Sie ausgefallene C ++ - Funktionen
Versuchen Sie in der Tat, C ++ überhaupt nicht zu verwenden. C ++ bringt Sie mit seinem RAII und seinen Konstruktoren und seiner impliziten Typkonvertierung "weiter vom Metall entfernt" ... Es ist viel schwieriger, alle Ihre Ressourcen genau zu erfassen, wenn Ihre Sprache hinter den Kulissen so viel tut. Wenn Sie wirklich eine bestimmte C ++ - Sprachfunktion verwenden möchten, versuchen Sie, den Ton Ihres Codes eher wie "C mit C ++ - Bits" als wie "C ++ in ein eingebettetes System eingebettet" beizubehalten. Das geht nie gut.
Vermeiden Sie insbesondere Generika / Vorlagen, die viel Speicherplatz beanspruchen. Jedes Mal, wenn Sie einen neuen Typ mit einer Vorlage in Ihrem Code verwenden, wird eine weitere Kopie der Vorlagenklasse in Ihr Programm kompiliert, nur für diesen einen Typ. Haben Sie sich jemals gefragt, warum C ++ - Binärdateien auf 20, 50 oder sogar 100 MB steigen können? Vorlagen, Mann. Vorlagen.
const
Dieses Schlüsselwort sollte so viele Variablen wie möglich enthalten. Wenn Sie diese Gewohnheit beibehalten, sparen Sie sowohl Ausführungszeit als auch Platzverbrauch. Lernen Sie den Unterschied zwischen
const T* foo
,T* const foo
undconst T* const foo
.quelle
vector<int>
jeweils einer von 10 Quelldateien werden etwas unhandlich, aber mit LTO kann der Compiler / Linker sie auf eine einzige gemeinsam genutzte Kopie deduplizieren. Wenn Sie die Funktionalität benötigen, ist der auf Vorlagen spezialisierte Code (insbesondere nach LTO-Inlining) häufig ziemlich ähnlich groß wie das, was Sie von Hand schreiben würden.static
wenn der Wert von Aufruf zu Aufruf gespeichert werden muss. In Bezug auf die Zuordnung ist ein statisches Lokal dasselbe wie ein globales.