Die Verwendung von malloc()
und free()
scheint in der Arduino-Welt ziemlich selten zu sein. Es wird viel häufiger, aber immer noch mit Vorsicht, in reinem AVR C verwendet.
Ist es eine wirklich schlechte Idee, malloc()
und free()
mit Arduino zu verwenden?
programming
sram
eeprom
Cybergibbons
quelle
quelle
Antworten:
Meine allgemeine Regel für eingebettete Systeme ist, nur
malloc()
große Puffer und nur einmal zu Beginn des Programms, zsetup()
. B. in . Das Problem tritt auf, wenn Sie Speicher zuordnen und freigeben. Während einer langfristigen Sitzung wird der Speicher fragmentiert, und schließlich schlägt eine Zuweisung fehl, weil ein ausreichend großer freier Bereich fehlt, obwohl der gesamte freie Speicher für die Anforderung mehr als ausreichend ist.(Historische Perspektive, bei Nichtinteresse überspringen): Abhängig von der Loader-Implementierung ist der einzige Vorteil der Laufzeitzuweisung gegenüber der Zuweisung zur Kompilierungszeit (initialisierte Globals) die Größe der Hex-Datei. Wenn eingebettete Systeme mit Standardcomputern mit vollständig flüchtigem Speicher erstellt wurden, wurde das Programm häufig von einem Netzwerk oder einem Instrumentierungscomputer auf das eingebettete System hochgeladen, und die Upload-Zeit war manchmal ein Problem. Das Auslassen von Puffern mit Nullen aus dem Bild kann die Zeit erheblich verkürzen.)
Wenn ich in einem eingebetteten System eine dynamische Speicherzuweisung benötige, ordne ich im Allgemeinen
malloc()
oder vorzugsweise statisch einen großen Pool zu und teile ihn in Puffer fester Größe (oder einen Pool mit jeweils kleinen und großen Puffern) und teile meine eigene Zuweisung / Freigabe aus diesem Pool. Dann wird jede Anforderung nach einer beliebigen Speichermenge bis zur festgelegten Puffergröße mit einem dieser Puffer berücksichtigt. Die aufrufende Funktion muss nicht wissen, ob sie größer als angefordert ist, und indem wir das Teilen und erneutes Kombinieren von Blöcken vermeiden, lösen wir die Fragmentierung. Natürlich kann es immer noch zu Speicherverlusten kommen, wenn das Programm Fehler zuweist / aufhebt.quelle
In der Regel vermeiden Sie beim Schreiben von Arduino-Skizzen die dynamische Zuordnung (sei es mit
malloc
odernew
für C ++ - Instanzen). Die Benutzer verwenden eher globalestatic
oder lokale (Stapel-) Variablen.Die Verwendung der dynamischen Zuordnung kann zu mehreren Problemen führen:
malloc
/free
Aufrufen), bei der der Heap größer wird als der aktuell zugewiesene SpeicherIn den meisten Situationen, mit denen ich konfrontiert war, war die dynamische Zuweisung entweder nicht erforderlich oder konnte mit Makros wie im folgenden Codebeispiel vermieden werden:
MySketch.ino
Dummy.h
Ohne
#define BUFFER_SIZE
, wenn wir wolltenDummy
Klasse eine nicht fest habenbuffer
Größe, hätten wir die dynamische Zuordnung wie folgt verwenden:In diesem Fall haben wir mehr Optionen als im ersten Beispiel (z. B. verwenden Sie unterschiedliche
Dummy
Objekte mit unterschiedlicherbuffer
Größe für jedes), es können jedoch Probleme mit der Heap-Fragmentierung auftreten.Beachten Sie, dass ein Destruktor verwendet wird, um sicherzustellen, dass der dynamisch zugewiesene Speicher
buffer
freigegeben wird, wenn eineDummy
Instanz gelöscht wird.quelle
Ich habe mir den
malloc()
von avr-libc verwendeten Algorithmus angesehen , und es scheint einige Verwendungsmuster zu geben, die im Hinblick auf die Heap-Fragmentierung sicher sind:1. Ordnen Sie nur langlebige Puffer zu
Damit meine ich: all das, was Sie zu Beginn des Programms benötigen, zuzuteilen und niemals freizugeben. In diesem Fall können Sie natürlich auch statische Puffer verwenden ...
2. Ordnen Sie nur kurzlebige Puffer zu
Das heißt, Sie geben den Puffer frei, bevor Sie etwas anderes zuweisen. Ein vernünftiges Beispiel könnte so aussehen:
Befindet sich kein Malloc im Inneren
do_whatever_with()
oder gibt diese Funktion alle zugewiesenen Elemente frei, sind Sie vor Fragmentierung geschützt.3. Geben Sie immer den zuletzt zugewiesenen Puffer frei
Dies ist eine Verallgemeinerung der beiden vorhergehenden Fälle. Wenn Sie den Heap wie einen Stapel verwenden (last in ist first out), verhält er sich wie ein Stapel und nicht wie ein Fragment. Es ist zu beachten, dass es in diesem Fall sicher ist, die Größe des zuletzt zugewiesenen Puffers mit zu ändern
realloc()
.4. Ordnen Sie immer die gleiche Größe zu
Dies verhindert keine Fragmentierung, ist jedoch in dem Sinne sicher, dass der Heap nicht größer wird als die maximal verwendete Größe. Wenn alle Puffer dieselbe Größe haben, können Sie sicher sein, dass der Steckplatz bei jeder Freigabe für spätere Zuweisungen verfügbar ist.
quelle
Die Verwendung der dynamischen Zuordnung (über
malloc
/free
odernew
/delete
) ist an sich nicht schlecht. Tatsächlich ist es für so etwas wie die Verarbeitung von Zeichenfolgen (z. B. über dasString
Objekt) oft sehr hilfreich. Das liegt daran, dass in vielen Skizzen mehrere kleine Zeichenfolgenfragmente verwendet werden, die schließlich zu einer größeren kombiniert werden. Wenn Sie die dynamische Zuordnung verwenden, können Sie nur so viel Speicher verwenden, wie Sie jeweils benötigen. Im Gegensatz dazu kann die Verwendung eines statischen Puffers mit fester Größe für jeden Puffer viel Speicherplatz verschwenden (was dazu führt, dass der Speicher viel schneller ausgeht), obwohl dies ausschließlich vom Kontext abhängt.Bei alledem ist es sehr wichtig, sicherzustellen, dass die Speichernutzung vorhersehbar ist. Wenn Sie der Skizze erlauben, abhängig von den Umständen zur Laufzeit (z. B. Eingabe) eine beliebige Menge an Speicher zu verwenden, kann dies früher oder später leicht zu Problemen führen. In einigen Fällen ist dies möglicherweise absolut sicher, z. B. wenn Sie wissen, dass die Verwendung niemals zu viel ergibt. Skizzen können sich jedoch während des Programmiervorgangs ändern. Eine früh gemachte Annahme könnte vergessen werden, wenn etwas später geändert wird, was zu einem unvorhergesehenen Problem führt.
Aus Gründen der Robustheit ist es normalerweise besser, mit Puffern mit fester Größe zu arbeiten und die Skizze so zu gestalten, dass sie von Anfang an explizit mit diesen Grenzwerten arbeitet. Das bedeutet, dass zukünftige Änderungen an der Skizze oder unerwartete Laufzeitumstände hoffentlich keine Speicherprobleme verursachen sollten.
quelle
Ich bin nicht einverstanden mit Leuten, die denken, man sollte es nicht benutzen oder es ist im Allgemeinen unnötig. Ich glaube, es kann gefährlich sein, wenn Sie nicht genau wissen, wie es aussieht, aber es ist nützlich. Es gibt Fälle, in denen ich die Größe einer Struktur oder eines Puffers (zur Kompilier- oder Laufzeit) nicht kenne (und auch nicht wissen sollte), insbesondere wenn es um Bibliotheken geht, die ich in die Welt versende. Ich bin damit einverstanden, dass Sie beim Kompilieren nur in dieser Größe backen sollten, wenn es sich bei Ihrer Anwendung nur um eine einzige bekannte Struktur handelt.
Beispiel: Ich habe eine serielle Paketklasse (eine Bibliothek), die Nutzdaten beliebiger Länge aufnehmen kann (Struktur, Array von uint16_t usw.). Am Ende dieser Klasse teilen Sie der Packet.send () -Methode einfach die Adresse des zu sendenden Objekts und den HardwareSerial-Port mit, über den Sie es senden möchten. Auf der Empfängerseite benötige ich jedoch einen dynamisch zugewiesenen Empfangspuffer, um die eingehende Nutzlast zu speichern, da diese Nutzlast zu jedem Zeitpunkt eine andere Struktur haben kann, zum Beispiel abhängig vom Status der Anwendung. WENN ich jemals nur eine einzelne Struktur hin und her sende, würde ich den Puffer einfach auf die Größe bringen, die er zum Zeitpunkt der Kompilierung haben muss. Aber in dem Fall, dass Pakete im Laufe der Zeit unterschiedlich lang sein können, sind malloc () und free () nicht so schlecht.
Ich habe tagelang Tests mit dem folgenden Code ausgeführt und ihn in einer Endlosschleife laufen lassen, und ich habe keine Hinweise auf eine Speicherfragmentierung gefunden. Nach dem Freigeben des dynamisch zugewiesenen Speichers kehrt der freie Betrag zu seinem vorherigen Wert zurück.
Ich habe keine Beeinträchtigung des Arbeitsspeichers oder meiner Fähigkeit, ihn mithilfe dieser Methode dynamisch zuzuweisen, festgestellt. Daher würde ich sagen, dass dies ein praktikables Tool ist. FWIW.
quelle
Die kurze Antwort lautet ja. Im Folgenden sind die Gründe aufgeführt, warum:
Es geht darum zu verstehen, was eine MPU ist und wie man innerhalb der Grenzen der verfügbaren Ressourcen programmiert. Das Arduino Uno verwendet eine ATmega328p- MPU mit 32 KB ISP-Flash-Speicher, 1024 KB EEPROM und 2 KB SRAM. Das sind nicht viele Speicherressourcen.
Denken Sie daran, dass der 2-KB-SRAM für alle globalen Variablen, Zeichenfolgenliterale, Stapel und die mögliche Verwendung des Heapspeichers verwendet wird. Der Stack muss auch Headroom für ein ISR haben.
Das Speicherlayout ist:
Heutzutage haben PCs / Laptops mehr als das 1.000.000-fache des Arbeitsspeichers. Ein Standardstapelspeicher von 1 MB pro Thread ist nicht ungewöhnlich, aber auf einer MPU völlig unrealistisch.
Ein eingebettetes Softwareprojekt muss ein Ressourcenbudget aufbringen. Dies ist eine Schätzung der ISR-Latenz, des erforderlichen Speicherplatzes, der Rechenleistung, der Befehlszyklen usw. Leider gibt es keine kostenlosen Mittagessen, und die harte eingebettete Echtzeitprogrammierung ist die am schwierigsten zu beherrschende Programmierfähigkeit.
quelle
Ok, ich weiß, dass dies eine alte Frage ist, aber je mehr ich die Antworten durchlese, desto mehr komme ich auf eine Bemerkung zurück, die hervorzuheben scheint.
Das haltende Problem ist real
Hier scheint ein Zusammenhang mit Turings Halteproblem zu bestehen. Das Zulassen einer dynamischen Allokation erhöht die Wahrscheinlichkeit des genannten "Anhaltens", sodass die Frage nach der Risikotoleranz gestellt wird. Es ist zwar praktisch, die Möglichkeit des
malloc()
Scheiterns usw. auszuschalten , aber es ist immer noch ein gültiges Ergebnis. Die Frage, die das OP stellt, scheint sich nur um Technik zu handeln, und ja, die Details der verwendeten Bibliotheken oder der spezifischen MPU spielen eine Rolle. Das Gespräch zielt darauf ab, das Risiko eines Programmstopps oder eines anderen abnormalen Endes zu verringern. Wir müssen das Vorhandensein von Umgebungen erkennen, in denen Risiken sehr unterschiedlich toleriert werden. Mein Hobbyprojekt, hübsche Farben auf einem LED-Streifen anzuzeigen, wird niemanden umbringen, wenn etwas Ungewöhnliches passiert, aber die MCU in einer Herz-Lungen-Maschine wird es wahrscheinlich tun.Hallo Herr Turing, mein Name ist Hybris
Für meinen LED-Streifen ist es mir egal, ob er blockiert, ich setze ihn einfach zurück. Wenn ich auf eine Herz-Lungen - Maschine durch eine MCU die Folgen davon Einsperren oder andernfalls zu betreiben sind buchstäblich Leben und Tod, so die Frage nach kontrolliert waren
malloc()
undfree()
sollte mit der Möglichkeit Spaltung zwischen dem, wie die vorgesehenen Programm befasst sich sein Herr zu demonstrieren Turings berühmtes Problem. Man kann leicht vergessen, dass es sich um einen mathematischen Beweis handelt, und sich davon überzeugen, dass wir es vermeiden können, die Grenzen der Berechnung zu überschreiten, wenn wir nur klug genug sind.Diese Frage sollte zwei akzeptierte Antworten haben, eine für diejenigen, die gezwungen sind zu blinken, wenn sie The Halting Problem ins Gesicht starren, und eine für alle anderen. Während die meisten Anwendungen des Arduino wahrscheinlich keine geschäftskritischen oder lebenswichtigen Anwendungen sind, ist die Unterscheidung immer noch vorhanden, unabhängig davon, welche MPU Sie codieren.
quelle
Nein, aber sie müssen sehr sorgfältig verwendet werden, um zugewiesenen Speicher freizugeben. Ich habe nie verstanden, warum Leute sagen, dass direktes Speichermanagement vermieden werden sollte, da es ein Maß an Inkompetenz impliziert, das im Allgemeinen nicht mit der Softwareentwicklung vereinbar ist.
Sagen wir, Sie verwenden Ihr Arduino, um eine Drohne zu steuern. Jeder Fehler in einem Teil Ihres Codes kann dazu führen, dass er vom Himmel fällt und jemanden oder etwas verletzt. Mit anderen Worten, wenn jemand nicht über die erforderlichen Fähigkeiten verfügt, um malloc zu verwenden, sollte er wahrscheinlich überhaupt nicht codieren, da es so viele andere Bereiche gibt, in denen kleine Fehler schwerwiegende Probleme verursachen können.
Sind die von malloc verursachten Fehler schwerer aufzuspüren und zu beheben? Ja, aber das ist eher eine Frage der Frustration des Programmierers als des Risikos. Was das Risiko betrifft, kann jeder Teil Ihres Codes genauso oder riskanter sein als malloc, wenn Sie nicht die Schritte unternehmen, um sicherzustellen, dass es richtig gemacht wird.
quelle