Hi,
ich arbeite gerade mit einem Mikrocontroller von NXP, den ich mit
MCUXpresso und C programmiere.
Um memory leaks zu verhindern, versuche ich auf dynamische
Datenstrukturen zu verzichten.
Mein Compiler erlaubt sowas, wobei ich max_len berechne:
1
uint32_tmax_len=10;
2
doubletest[max_len];
Kann mir jemand sagen, was da im Hintergrund passiert und ob das sicher
ist?
Vielen Dank!
mentine schrieb:> Kann mir jemand sagen, was da im Hintergrund passiert und ob das sicher> ist?
Es ist unsicherer als malloc().
malloc() kann Vorkehrungen enthalten, um Speicherengpässe zu erkennen
und dann NULL zurückzugeben.
Deine dynamische Allokation dagegen erfolgt auf dem Stack, und sofern du
nicht irgendwelche Hardwaremaßnahmen hast (MMU oder einen limitierten
Stack, bei dessen Überlauf ein Hardfault triggert, weil bspw. an der
Stelle gar kein Speicher mehr sitzt), wirst du bei einem Überlauf
fröhlich andere Daten überschreiben.
Das funktionale Pendant dazu ist übrigens alloca().
mentine schrieb:> Kann mir jemand sagen, was da im Hintergrund passiert und ob das sicher> ist?
Der nötige Speicher wird temporär auf dem Stack angelegt und
entsprechend kannst Du Dir damit einen Stack overflow erzeugen. Da dass
array so nicht initialisiert wird, kann es sogar sein, dass Du erst auf
entsprechende Elemente des arrays zugreifen musst, damit die
Auswirkungen des stack overflows sichtbar werden.
mentine schrieb:> Mir geht es ja gerade darum, dass max_len nicht const ist.
Wieso?
Das ist kein dynamisches Array.
Ein dynamisches Array kann seine Größe zur Laufzeit ändern.
Das ist hier nicht der Fall.
Darum ist const goldrichtig.
1
voidfun(constsize_tn)
2
{
3
intarr[n];
4
// ......
5
}
6
intmain()
7
{
8
fun(6);
9
}
Zudem war vorher nicht zu erkennen, dass das Array lokal ist.
Aber dazu haben dir die anderen schon was gesagt.
Das reserviert halt Speicher auf dem Stack. Im grunde das selbe wie
alloca: https://www.man7.org/linux/man-pages/man3/alloca.3.html
Ich würde aber in der Regel eher von der Verwendung davon abraten. Nam
weiss nie so genau, wie viel Platz da auf dem Stack noch übrig ist.
Mindestens würde ich begrenzen, wie viel man auf dem Stack maximal
reserviert.
Arduino Fanboy D. schrieb:> Zudem war vorher nicht zu erkennen, dass das Array lokal ist.> Aber dazu haben dir die anderen schon was gesagt.
Ja das hätte ich ein bisschen besser formulieren können.
Die Infos zu alloca war das was ich gesucht habe, danke.
Ist es denn schlimm malloc im embedded Umfeld zu benutzen? Ich weiß zur
Laufzeit wie groß meine Arrays zum Berechnen von Zwischenschritten
maximal sein müssen, ich muss sie also nicht "vergrößern".
Muss ich da wirklich jedem Array absolut maximalen Speicherplatz
reservieren?
mentine schrieb:> Ist es denn schlimm malloc im embedded Umfeld zu benutzen?
Kommt drauf an. "embedded" reicht vom kleinen 1kB PIC bis zu fetten
32Bit ARMs.
> Ich weiß zur> Laufzeit wie groß meine Arrays zum Berechnen von Zwischenschritten> maximal sein müssen, ich muss sie also nicht "vergrößern".> Muss ich da wirklich jedem Array absolut maximalen Speicherplatz> reservieren?
Was spricht gegen ein statisches Array mit maximaler Größe? Ist dein
Speicherbedarf in anderen Programmteilen so dynamisch, daß der RAM in
Summe dann nicht mehr reicht? nur dann wäre eine dynamische
Speicherverwaltung wirklich nötig. Oder könnte man das eine, große,
statische Array nicht abwechselnd in den verschiedenen Programmteilen
nutzen, die sich dann logischerweise im Ablauf nicht überschneiden
dürfen.
In einem "embedded System" ist es meist erwünschenswert, dass man den
Speicher zur Verfügung hat, wenn man ihn braucht.
Und: Für freien Speicher gibts kein Geld zurück.
Aus der Dokumentation der avr libc
"Returns:
alloca() returns a pointer to the beginning of the allocated space. If
the allocation
causes stack overflow, program behaviour is undefined."
Was bringt mir die Funktion dann im Gegensatz zum Array, das mit
variabler Größe erstellt wird? Sollte da nicht wenigstens ein stack
overflow verhindert werden können? Schließlich kennt der Compiler heap
und stack.
mentine schrieb:> Ist es denn schlimm malloc im embedded Umfeld zu benutzen?
Viele werden dir hier erzählen, dass man das nie und nimmer gar nicht
macht.
Ich mach's seit Jahren. ;-)
Wenn du ein wirklich dynamisches Problem hast (bei dem du vorher also
nicht weißt, welche Anforderungen du in welcher Größe zu welcher Zeit
bekommst), dann ist malloc() mehr als sinnvoll, denn es ist zumindest
ein sauber bis zu Ende debuggtes System (davon sollte man jedenfalls
erstmal ausgehen). Jede Eigenbau-Lösung hat an der Stelle die Chance,
stattdessen neue Bugs einzubringen und die eintreffenden Anforderungen
schlechter zu erfüllen (bspw. wenn du statisch N Slots mit maximaler
Anforderungsgröße vorab allozierst, während die mittlere
Anforderungsgröße am Ende viel kleiner ist => dein Speicher ist dann
viel schneller am Ende).
Aber: wenn du ein derartig dynamisches Problem hast, egal welche Form
von Speicherverwaltung du nimmst, du brauchst dann immer einen Plan B,
was getan werden soll, wenn sich die eintreffenden Anforderungen zu
irgendeinem Zeitpunkt nicht erfüllen lassen. Das kann vom Wegwerfen
einzelner Anforderungen bis zu einem Reboot alles Mögliche sein, aber es
muss vorher klar sein, was in der Situation zu tun ist und welche
Konsequenzen das hat.
Wenn du kein dynamisches Problem hast, brauchst du normalerweise auch
kein malloc(). Wenn es ein pseudostatisches Problem ist (bspw. Belegung
von ein paar Puffern in stdio beim Start, die dann die Lebenszeit dort
bleiben), verursacht malloc() ein bisschen mehr Overhead als eine
Vorab-Allokation, dafür braucht man im Gegenzug keine großen Klimmzüge
veranstalten (setvbuf() im Falle von stdio).
Es gibt tatsächlich keinen Unterschied zwischen diesen variable-length
arrays und alloca(), beide funktionieren nach dem gleichen Prinzip.
Falk B. schrieb:> Schließlich kennt der Compiler heap und stack.
Nein, der Compiler kennt beides nicht. Der Heap wird durch die
Bibliothek verwaltet, der Stack durch die CPU.
Daher schrieb ich ja oben: malloc() kann bei Speichermangel zumindest
überhaupt so implementiert werden, dass es reagiert. Die dynamische
Allokation auf dem Stack vermag das höchstens in dem Falle, dass die
Hardware irgendwie den Stack limitiert. Dann ist der von mir gerade
genannte "Plan B" aber der harte Aufruf des zugehörigen Fault-Handlers.
Falk B. schrieb:> Was bringt mir die Funktion dann im Gegensatz zum Array, das mit> variabler Größe erstellt wird?
Alloca gibt es einfach schon länger, vor- oder nachteile hat es keine.
Jörg W. schrieb:> Nein, der Compiler kennt beides nicht. Der Heap wird durch die> Bibliothek verwaltet, der Stack durch die CPU.
Na komm, so einfach ist es doch nicht. Der Compiler legt über
Linkerscript etc. den Stack fest. Dito den Heap. Der Compiler kennt alle
statischen/globalen Variablen. Damit weiß er, wieviel Luft zwischen Heap
und Stack ist. Das kann er auch dynamisch berechnen, so man es denn
will. Klar, die meisten Implementation der libc machen es NICHT.
Ok, dann gehe ich auf jeden Fall von der alloca Implementierung weg.
Ich verwende das Entwicklungsboard:
https://www.nxp.com/design/development-boards/i-mx-evaluation-and-development-boards/mimxrt1060-evk-i-mx-rt1060-evaluation-kit:MIMXRT1060-EVK
An Speicher mangelt es mir da auf keinen Fall.
Konkret geht es darum, dass ich Teile der SciPy-Bibliothek zur
Koeffizientenberechnung von Butterworthfiltern in C übersetzt habe. Ich
benutze eine ganze Menge Arrays, die nicht besonders groß sind und nur
zur Berechnung von Koeffizienten benutzt werden. Die Größe der Arrays
hängt von der order des Filters ab.
Natürlich könnte ich dort alle Arrays irgendwie auf eine Maximalgröße
deckeln. Dann müsste ich aber jedesmal ausrechnen ob ich nicht die Größe
überschreite, das birgt zusätzlich nochmal potential für Bugs.
Nein, der Compiler kennt nichts davon. Die Symbole werden sowieso erst
durch den Linker eingetragen.
Die Bibliothek kann aber auf diese Symbole zugreifen, und diese
verwaltet den Heap. Damit kann sie auch feststellen, ob da noch Platz
ist. Das ist, wie malloc() (bzw. seine Hintermänner und -frauen :)
arbeiten.
alloca() ist, auch wenn das so aussieht, keine Bibliotheksfunktion,
sondern tatsächlich das nackte Compiler-Interface, welches Platz im
Stack einräumt. Was und wo der Heap ist und wie der implementiert ist,
weiß im Compiler absolut niemand (und daher auch weder alloca() noch die
dynamischen Arrays).
Du musst immer dran denken, an welcher Stelle der Compiler seine
Entscheidungen treffen muss: zur Compilezeit.
Ja, man könnte ein alloca() sicher auch so implementieren, dass es
vorher irgendwelche Tests macht, aber dann wäre die Einfachheit (und
Schnelligkeit) des Interfaces dahin, und diese sind die einzige
Berechtigung, dafür überhaupt separate Interfaces zu haben. Wenn man das
nicht braucht, kann man jederzeit auch gleich malloc() benutzen, dann
hat man die Tests mit an Bord, und solange man malloc() mit gleichen
Restriktionen wie alloca() benutzt (also nur innerhalb einer Funktion,
und am Ende wird alles wieder dealloziert), dann gibt's auch mit
malloc() niemals die immer wieder mal ins Feld geführten Probleme mit
einer Speicherfragmentierung.
mentine schrieb:> Natürlich könnte ich dort alle Arrays irgendwie auf eine Maximalgröße> deckeln.
Das wäre dann die FORTRAN-Methode. ;-)
> Dann müsste ich aber jedesmal ausrechnen ob ich nicht die Größe> überschreite, das birgt zusätzlich nochmal potential für Bugs.
So ist es.
Da du mit einem ARM arbeitest, benutzt du die newlib? Da ist die
Methode, mehr Speicher für den Heap zuzuordnen, in der Funktion _sbrk()
zu implementieren, die deine Applikation bereitstellen muss. Da hast du
auch alle Freiheiten, ausreichend Tests durchzuführen, ob noch genügend
Speicherreserve vorhanden ist, bevor du der Allokation zustimmst. Aber
wie du schon schreibst, auf einer Hardware mit ohnehin genügend
RAM-Reserven wird das alles gar kein Problem sein.
Bauform B. schrieb:> Ein Hard Fault ist sooo viel angenehmer als vom Stack überschriebene> Daten.
Aber viel unangenehmer als ein Dialog mit dem Anwender, der demjenigen
mitteilt, dass seine gewünschte Konfiguration die verfügbaren Ressourcen
übersteigt.
Also ja, MMU nutzen ist OK (kann an anderen Stellen helfen), aber ich
würde für diese konkrete Konstellation trotzdem malloc() nehmen und
ausreichend Sicherheit in der Heap-Allokation vorsehen. Dann kann man
bei Überforderung einen "sauberen Rückzieher" machen.
Eine Funktion, die Speicher holt, Mathematik macht und den Speicher
wieder freigibt wird den Heap auch nicht zerklüften. Zumindest solange
kein RTOS im Spiel ist.
Und wenn wg. RTOS andere Tasks unterbrechen und auch wieder Speicher
anfordern können, dann gibt es noch die Möglichkeit Memory Pools zu
bauen.
Für berechnete Indizes ist eine Validierung in einem assert macro im
debug build auch hilfreich.
**Ergebnis** :
n=10
pi@test:~/test $ ./a.out 10
sizeof int: 4, sizeof test_array: 40, &test_array: 2127910168, &ende:
2127910220
**Das dynamische Array sollte sicher sein** :
1. das Array hat die richtige Größe
2. es wird am Stack genügend Speicherplatz reserviert
Es werden sogar 12 Byte mehr als notwendig reserviert. Warum?
Gerald K. schrieb:> Warum?
weil der Compiler die Daten nicht in dieser Reihenfolge auf den Stack
legen muss, oder ein anderes alignement hat.
So wird die Differenz wie erwartet: