Forum: Mikrocontroller und Digitale Elektronik Dynamisches Array im embedded System


von mentine (Gast)


Lesenswert?

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_t max_len = 10;
2
double test[max_len];

Kann mir jemand sagen, was da im Hintergrund passiert und ob das sicher 
ist?

Vielen Dank!

von Einer K. (Gast)


Lesenswert?

Würde bei mir so aussehen:
> const size_t max_len = 10;
> double test[max_len];

Und ja, das ist so sicher, wie es in C nur sein kann

von Thilo L. (bc107)


Lesenswert?

... natürlich nur solange, bis der Programmierer auf test[max_len] 
zugreift...

von mentine (Gast)


Lesenswert?

Mir geht es ja gerade darum, dass max_len nicht const ist.
Mit C99 geht kann ich max_len bei jedem Funktionsaufruf neu berechnen.
1
void fun(int n)
2
{
3
  int arr[n];
4
  // ......
5
} 
6
int main()
7
{
8
   fun(6);
9
}

Wie hier beschrieben:
https://www.geeksforgeeks.org/variable-length-arrays-in-c-and-c/

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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().

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?

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
void fun(const size_t n)
2
{
3
  int arr[n];
4
  // ......
5
} 
6
int main()
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.

von 🐧 DPA 🐧 (Gast)


Lesenswert?

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.

von mentine (Gast)


Lesenswert?

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?

von Falk B. (falk)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von 🐧 DPA 🐧 (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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.

von mentine (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Bauform B. (bauformb)


Lesenswert?

Außerdem hat die Hardware eine MPU, nutze sie! Ein Hard Fault ist sooo 
viel angenehmer als vom Stack überschriebene Daten.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Gerald K. (geku)


Lesenswert?

**Testprogramm** :
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
void test(int n)
6
{
7
        int test_array[n];
8
        int ende;
9
10
        printf("sizeof int: %d, sizeof test_array: %d, &test_array: %d, &ende: %d\r\n",
11
        sizeof(int),sizeof(test_array),&test_array[0],&ende);
12
}
13
14
int main(int argc, char* argv[])
15
{
16
        test(atoi(argv[1]));
17
}

**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?

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

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:
1
void test(int n)
2
{
3
    struct
4
    {
5
        int test_array[n];
6
        int ende;
7
    } s;
8
    
9
    printf("sizeof int: %ld, sizeof test_array: %ld, test_array: %p, ende: %p, diff: %ld\r\n",
10
           sizeof(int), sizeof(s.test_array), &s.test_array[0], &s.ende, (void*)&s.ende - (void*)&s.test_array[0]);
11
}

was aber nicht unbedingt Thema des threads war.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.