Forum: Mikrocontroller und Digitale Elektronik Speicherverwaltung bei modularer Echtzeitsoftware


von Alfons H. (alfonshans)


Lesenswert?

Hallo,
ich stehe momentan vor der Aufgabe meinen Source code für zukünftige 
Projekte modularer zu gestalten. Als Beispiel soll hier ein einfacher 
Mittelwertfilter dienen.

Ich könnte jetzt den Mittelwertfilter als Modul definieren und den 
Speicher extra als Variable deklarieren. Das Modul wäre für alle 
Speichergrößen gleich. Die Länge wäre von der Speichergröße und der 
Initialisierung abhängig. Ich benötige aber keine flexible Länge die 
sich zur Laufzeit ändert. Ich muss jezt aber für jeden Filter den ich 
benutze den Speicher mit definierter Länge anlegen. Wahrscheinlich kann 
man dass über Macros usw. machen. Es bleiben aber wohl immer min. 2 
Zeilen eine für den Speicher eine für die Initialisierung.

Nun wäre die Frage ob man bei der Initialisierung nicht den Speicher für 
den Filter mittels malloc im Heap anlegen könnte. Diese Speicher würden 
nie wieder freigegeben. Somit würde auch keine fragmentierung auftreten. 
Auch die Laufzeit wäre immer gleich weil der Ablauf der allocation immer 
gleich wäre. Natürlich wäre die Systemstartzeit etwas erhöht.

Gibt es andere Auswirkungen, die ich jetzt nicht auf dem Schirm habe? 
Oder ist das aus anderen Gründen ein NoGo bei der (sehr) hardwarenahen 
Softwareentwicklung?

von Oliver S. (oliverso)


Lesenswert?

Alfons H. schrieb:
> Es bleiben aber wohl immer min. 2 Zeilen eine für den Speicher eine für
> die Initialisierung.

Wenn du dem implizit genannten C noch ein ++ gönnst, geht's in einer.

Oliver

von Alfons H. (alfonshans)


Lesenswert?

Sorry. Hatte ich vergessen. Es geht um C. Nicht C++

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Alfons H. schrieb:
> Sorry. Hatte ich vergessen. Es geht um C. Nicht C++

Darf ich fragen, warum man sich freiwillig eine solche Kugel ans Bein 
bindet?

(und nein, ich brauche/erwarte keine Antwort auf die Frage)

von Bruno V. (bruno_v)


Lesenswert?

Alfons H. schrieb:
> Ich könnte jetzt den Mittelwertfilter als Modul definieren und den
> Speicher extra als Variable deklarieren.

Ich lasse der Applikation beim Init einen Speicher notwendiger Größe 
übergeben. Pseudo Beispiel (ohne Fehlermeldung, Success etc).

Die Applikation stellt das RAM bereit, wie sie will. Stack, malloc, 
static, ... . Und Modul richtet alle instanzabhängigen Daten darin ein.

Wenn es nur eine Instanz gibt, fällt struct sModul für Header und 
Applikation auch noch weg. Dann ist nur der Speicher für N-Elemente zu 
übergeben. Teils auch anders rum: Du übergibst x Byte Speicher und 
bekommst die Anzahl an Elementen, die reinpassen.
1
/*modul.h*/
2
#define MODUL_RAM_SIZE(Elemente) (128+(14*Elemente))
3
struct sModul; /* Inhalt bleibt der Applikation verborgen! Nur Ptr! */
4
struct sModul *ModulInit(unsigned char *ram, int ram_size);
5
6
/* application.c */
7
struct sModul *ModulHdl;
8
static unsigned char modul_ram[MODUL_RAM_SIZE(100)];
9
10
   ModulHdl = ModulInit(modul_ram, sizeof(modul_ram));
11
12
   ModulDoThis(ModulHdl);
13
   ModulAddThat(ModulHdl, xy);
14
   z = ModulGetThose(ModulHdl);
15
16
17
/*modul.c*/
18
struct sModul
19
{
20
   int ...
21
};
22
23
struct sElement
24
{
25
   unsigned char ...
26
};
27
28
struct sModulHdl *ModulInit(unisgned char *ram, int ram_size)
29
{
30
struct sModulHdl *m = (struct sModulHdl*) ram;
31
    ....
32
    return m;      
33
}

von Lu (oszi45)


Lesenswert?

Alfons H. schrieb:
> Diese Speicher würden nie wieder freigegeben.

Bei Echtzeit ist natürlich schnelle Reaktion erwünscht. So gesehen, kann 
das vorteilhaft sein (bis das nächste SW-Update etwas MEHR Speicher 
möchte).

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Alfons H. schrieb:
> Auch die Laufzeit wäre immer gleich weil der Ablauf der allocation immer
> gleich wäre.

Die Laufzeit wäre bei den Durchläufen gleich, aber nicht zwangsläufig 
identisch zu der bei einer schon zur Compilezeit bekannten 
Speicheradresse.

von Alfons H. (alfonshans)


Lesenswert?

@Bruno
ja. Das ist klar. Aber hier muss man eben die Struktur und den Speicher 
getrennt instanziieren. Mit malloc in der Init funktion würde das 
automatisch geschehen. Nur liegt der Speicher halt nicht bei den 
globalen Variablen sondern im heap

von Alfons H. (alfonshans)


Lesenswert?

Lu schrieb:
> Alfons H. schrieb:
>> Diese Speicher würden nie wieder freigegeben.
>
> Bei Echtzeit ist natürlich schnelle Reaktion erwünscht. So gesehen, kann
> das vorteilhaft sein (bis das nächste SW-Update etwas MEHR Speicher
> möchte).

Ich gehe davon aus, dass bei einem softwareupdate der Aufbau des RAMs 
der Applikation sich sowieso ändert.

von Alfons H. (alfonshans)


Lesenswert?

Walter T. schrieb:
> Alfons H. schrieb:
>> Auch die Laufzeit wäre immer gleich weil der Ablauf der allocation immer
>> gleich wäre.
>
> Die Laufzeit wäre bei den Durchläufen gleich, aber nicht zwangsläufig
> identisch zu der bei einer schon zur Compilezeit bekannten
> Speicheradresse.

Ist die Frage ob der Compiler versteht, dass wenn ich 3 Strukturen mit 
pointer auf festen Speicher erstelle und diese dem Modul übergebe dass 
er das besser optimieren kann als wenn die Adressen zu dieser Zeit noch 
nicht fest stehen.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Lu schrieb:
> Bei Echtzeit ist natürlich schnelle Reaktion erwünscht.
Ist das so?

Besser:
> Der Begriff Echtzeit charakterisiert den Betrieb von Echtzeitsystemen, die 
bestimmte Ergebnisse zuverlässig innerhalb einer vorbestimmten Zeitspanne, zum 
Beispiel in einem festen Zeitraster, liefern können

Das kann möglichst schnell bedeuten, muss aber nicht. Zu schnell kann 
sogar kontraproduktiv sein.

von Walter T. (nicolas)


Lesenswert?

Alfons H. schrieb:
> [ob]
> er das besser optimieren kann als wenn die Adressen zu dieser Zeit noch
> nicht fest stehen.

Bei Inline-Funktionen: Ja, allerdings habe ich eher ältere GCC-Versionen 
getestet.

Aber meistens wird das nicht viel ausmachen, im Idealfall nur das laden 
der Offset-Adresse.

von Alexander (alxc)


Lesenswert?

Wenn ich das richtig verstehe dann geht es um den ewigen Streit ob man 
dynamische Speicher (Heap) benutzen darf oder ob das böse ist.

Die Antwort lautet: kommt drauf an. Das Internet ist voll von Artikeln 
darüber. Chat GPT wird dir die Vor- und Nachteile ausführlichst erklären 
wenn du deine Randbedingung nennst.

Die kurze Antwort: Ja klar ist es in Ordnung, dynamischen Speicher bei 
der Initialisierung, also zur Laufzeit zu reservieren. Du darfst halt 
niemals in die Lage kommen dass dir der Speicher zur Laufzeit ausgeht. 
Dafür das von vornherein auszuschließen, gibt’s Techniken.

von Bruno V. (bruno_v)


Lesenswert?

Alfons H. schrieb:
> Mit malloc in der Init funktion würde das
> automatisch geschehen. Nur liegt der Speicher halt nicht bei den
> globalen Variablen sondern im heap

 + Du brauchts malloc (manchmal unerwünscht, weil sich nicht jeder an 
"nur zu Beginn" hält)
 + keine Freigabe/Wiederverwendung möglich
 + nicht alternativ auf Stack möglich
 + keine statische Prüfung des Speicherplatzes

Wenn der Aufrufer den Speicherplatz bereitstellt, werden viele Dinge im 
Modul viel einfacher. Gehst Du dann über zu beliebig vielen Instanzen 
(auch temporär) wird es noch einfacher, weil alle lokalen Variablen 
wegfallen. Man ist dann zu einem sauberen Design gezwungen. Und der 
Linker meckert sofort, wenn man sich beim Speicher total verschätzt hat.

Um die Laufzeit wegen der Indirektion brauchst Du Dir keine Gedanken 
machen.

: Bearbeitet durch User
von Jan K. (jan_k776)


Lesenswert?

Du brauchst kein malloc, kannst doch den Speicher im caller auch 
statisch anlegen. Dann kann der Compiler auch besser prüfen, ob es 
overflows gibt. Wenn sich die Anzahl der Mittelwertfilter Instanzen oder 
deren Pufferlänge nicht ändern zur Laufzeit ist doch alles tutti. In C 
geht's mit Makros, in C++ mit constexpr

von Nemopuk (nemopuk)


Lesenswert?

Alfons H. schrieb:
> Nun wäre die Frage ob man bei der Initialisierung nicht den Speicher für
> den Filter mittels malloc im Heap anlegen könnte. Diese Speicher würden
> nie wieder freigegeben.

Ja, das kannst du so machen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

In C++ wär's einfach:
1
#include <cstddef>
2
#include <cstdint>
3
4
template <std::size_t Depth>
5
struct Filter {
6
  std::int32_t lastValues [Depth];
7
  Filter ();
8
9
  void run ();
10
};
11
12
template <std::size_t Depth>
13
Filter<Depth>::Filter () {
14
  // Irgendwas initialisieren...
15
  for (auto& x : lastValues)
16
    x = 42;
17
}
18
19
template <std::size_t Depth>
20
void Filter<Depth>::run () {
21
}
22
23
Filter<8> filter1;
24
Filter<4> filter2;
25
26
int main () {
27
  filter1.run ();
28
  filter2.run ();
29
}

Die C++ -Lösung würde auch dieses Problem ersparen:

Alfons H. schrieb:
> Natürlich wäre die Systemstartzeit etwas erhöht.

Und der Linker würde dir direkt sagen können wie viel Speicher benutzt 
wird und wie viel noch frei ist, statt dass du dafür erst flashen musst.

Alfons H. schrieb:
> Es geht um C. Nicht C++

Aber dann geht's natürlich nicht.

von Alfons H. (alfonshans)


Lesenswert?

Jan K. schrieb:
> In C
> geht's mit Makros, in C++ mit constexpr

Willst du mir ein Beispiel zeigen wie das geht?

von Jan K. (jan_k776)


Lesenswert?

Alfons H. schrieb:
> Jan K. schrieb:
>> In C
>> geht's mit Makros, in C++ mit constexpr
>
> Willst du mir ein Beispiel zeigen wie das geht?

Ziemlich genau so, wie hier von Bruno schon gezeigt wurde: 
Beitrag "Re: Speicherverwaltung bei modularer Echtzeitsoftware"

Die Idee ist also, dass der Aufrufer den Speicher reserviert und auch 
die Instanz struct erstellt, nicht dein Filter Modul. Die Instanz struct 
wird per Zeiger übergeben und ist somit dein "Objekt" des Filtermoduls. 
Damit wird der state des Filters zu 100% beschrieben und du brauchst 
keine static variable mehr im Filtermodul und du kannst, so es denn 
gewünscht ist, beliebig viele Filter "Objekte" parallel erstellen.

Für C++ hat es Niklas über mir schon gezeigt. Braucht sogar kein 
constexpr, nur templates.

von Bruno V. (bruno_v)


Lesenswert?

Jan K. schrieb:
> dass der Aufrufer den Speicher reserviert und auch
> die Instanz struct erstellt, nicht dein Filter Modul.

den Speicher ja, aber die Instanz des structs erstellt die Init-Funktion 
des Moduls "in" dem reserviertem Speicher.

Das struct selber ist der Applikation gar nicht bekannt. Wenn an nur 
einen Ptr davon verwendet (woanders "Händler/hdl" genannt), ist das 
typsicher und OK.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> ist das typsicher und OK.

Naja, du musst sicher sein dass in MODUL_RAM_SIZE die Größe des struct 
korrekt eingetragen ist, und die kann schnell mal geändert werden, und 
kann auch je nach Plattform oder Compiler -Optionen anders sein. 
Besonders "sicher" finde ich das nicht.

Außerdem fehlt da ein _Alignas bei "modul_ram"; das muss ebenso händisch 
konsistent gehalten werden.

von Bruno V. (bruno_v)


Lesenswert?

Niklas G. schrieb:
> Naja, du musst sicher sein dass in MODUL_RAM_SIZE die Größe des struct
> korrekt eingetragen ist, und die kann schnell mal geändert werden, und
> kann auch je nach Plattform oder Compiler -Optionen anders sein.

Sie muss nur >= sein und per CT_ASSERT abgesichert. Die Alternative ist, 
die Strukturen zu veröffentlichen. Das ist zwar in C++(-Klassen) üblich, 
aber möglichst zu vermeiden.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> Sie muss nur >= sein und per CT_ASSERT abgesichert

Wenn sie zu groß ist, verschwendet man Speicher. Man sollte also genau 
treffen. Dann braucht es aber Fallunterscheidungen je nach Architektur, 
Compiler -Optionen etc.

Bruno V. schrieb:
> Das ist zwar in C++(-Klassen) üblich, aber möglichst zu vermeiden.

Naja, ein Embedded-Projekt das so groß ist dass das zum Problem wird, 
kann wahrscheinlich auch einfach "new" benutzen, weil dann eh massig 
(SD)RAM vorhanden ist.

von Andreas M. (amesser)


Lesenswert?

Alfons H. schrieb:
> Nun wäre die Frage ob man bei der Initialisierung nicht den Speicher für
> den Filter mittels malloc im Heap anlegen könnte. Diese Speicher würden
> nie wieder freigegeben. Somit würde auch keine fragmentierung auftreten.
> Auch die Laufzeit wäre immer gleich weil der Ablauf der allocation immer
> gleich wäre. Natürlich wäre die Systemstartzeit etwas erhöht.

malloc beim Systemstart ist nicht unübliches und ein gangbarer Weg. 
Einen vordefinierten Speicherbereich zu übergeben und darauf zu hoffen, 
das der dem Aufgerufenen langt erleichter die Pflege und Integration 
eher nicht. Dann lieber wirklich die gesamte Resourcenstruktur "public" 
machen. Bei C++ ist das quasi der Normalfall. (Ja wir werden bei uns 
auch zu C genötigt, nervt aber was solls...)

In manchen Fällen hat malloc/free auch zur Laufzeit Sinn, z.B. wenn es 
sich um Zusatzfunktionen handelt, wo man mit leben kann wenn die mal 
temporär nicht tun. (Z.B. Integrierte Webserver, Dateisysteme etc). Bei 
uns ist es so, dass beim Systemstart alle Module ihren benötigten RAM so 
weit es geht per malloc holen, manche Module machen es erst in der 
Konfigurationsphase. Zur Laufzeit ist Malloc dann in allen Echtzeit und 
Hauptfunktionen jedoch quasi "verboten". Das hat auch den Vorteil das 
man sich die ganze Fehlerbehandlung sparen kann, da es per Definition 
zur Laufzeit kein "Out Of Memory" geben kann.

von Bruno V. (bruno_v)


Lesenswert?

Niklas G. schrieb:
> Naja, ein Embedded-Projekt das so groß ist dass das zum Problem wird,

Dabei geht es nur darum, Internas von Modulen nicht unnötig zu 
veröffentlichen. Egal ob für einen µC mit 1k oder 1G.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> Dabei geht es nur darum, Internas von Modulen nicht unnötig zu
> veröffentlichen

In C++ gibt's dafür "private".

von Bruno V. (bruno_v)


Lesenswert?

Niklas G. schrieb:
> In C++ gibt's dafür "private".

private schränkt den Zugriff ein. Nicht die Veröffentlichung. (Das soll 
aber kein C/C++ Dingen werden: In beiden kann man natürlich auch nur 
Teile veröffentlichen und den Rest lokal erweitern)

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> Nicht die Veröffentlichung.

Warum möchtest du die Interna eines Moduls für andere Komponenten der 
selben Anwendung (!) geheim halten?

Der Preis für statische Speicherverwaltung ist dass man die Größen der 
Klassen kennt. Der Compiler kann da wunderbar automatisch für sorgen. 
Kein schlechter Preis finde ich. Nervig wird das nur bei sehr großen 
Projekten, wo eine Änderung einer Klasse eine Neukompilation vieler 
anderer Dateien nach sich zieht, aber wie gesagt sind das nicht die 
Projekte, wo statische Speicherverwaltung wichtig ist.

von G. K. (zumsel)


Lesenswert?

Alfons H. schrieb:

> Ist die Frage ob der Compiler versteht, dass wenn ich 3 Strukturen mit
> pointer auf festen Speicher erstelle und diese dem Modul übergebe dass
> er das besser optimieren kann als wenn die Adressen zu dieser Zeit noch
> nicht fest stehen.

Soll es möglichst schnell sein oder schnell genug?

von Alfons H. (alfonshans)


Lesenswert?

G. K. schrieb:
> Alfons H. schrieb:
>
>> Ist die Frage ob der Compiler versteht, dass wenn ich 3 Strukturen mit
>> pointer auf festen Speicher erstelle und diese dem Modul übergebe dass
>> er das besser optimieren kann als wenn die Adressen zu dieser Zeit noch
>> nicht fest stehen.
>
> Soll es möglichst schnell sein oder schnell genug?

Natürlich nur schnell genug. Also ein paar Takte für indirekte 
Adressierung usw. sind kein Problem. Mir ist klar dass es immer ein 
Kompromiss wird. Zu viel Abstraktion lohnt sich auch nicht, da man dann 
ja praktisch bei C++ landet und man hier das Rad nicht neu erfinden 
müsste.

Mir ging es nur darum ob ich etwas übersehe bei der Verwendung von 
Speicherallokation zur Laufzeit.

Bleibt die Frage wie das meine Kollegen sehen, da malloc bei 
hardwarenahen Entwicklern ja doch den Ruf hat etwas "Böses", was man auf 
keinen Fall verwendet, zu sein scheint.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Mal abgesehen von irgendwelchen exotischen Prozessoren, die mit 
unterschiedlich zu adressierenden RAM-Bereichen arbeiten koennen, wirds 
doch ausser fuer die Position im RAM voellig wurst sein, ob ich meinen 
Pointer auf ein Stueck RAM nun per
1
ptr = calloc(RUELPS, sizeof(sample_t));
 oder per
1
static sample_t ptr[RUELPS];
 kriege.

Ich wuerde mal vermuten, dass es da bei den Modularisierungbemuehungen 
noch deutlich lohnendere Ziele geben wird.

Gruss
WK

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Dergute W. schrieb:
> wirds
> doch ausser fuer die Position im RAM voellig wurst sein,

- Der Aufruf an calloc/malloc/new kostet (etwas) Laufzeit und natürlich 
Flash
- Man sieht beim Kompilieren/Linken nicht den gesamten RAM-Verbrauch 
sondern kann ihn nur durch Ausführen ermitteln
- Man muss alle malloc-Aufrufe mit Fehlerbehandlung versehen
- Ist der Verbrauch zu hoch, bemerkt man dies erst beim Ausführen und 
muss dann ggf. mühselig nachvollziehen, welcher der (ggf. vielen) 
malloc-Aufrufe zu viel Speicher anfordert (nicht notwendigerweise der 
Aufruf, der dann 0 zurückgibt weil kein Speicher mehr frei ist)
- Der Linker kann unbenutzte dynamisch angeforderte Speicherbereiche 
nicht wegoptimieren, auch wenn nie darauf zugegriffen wird; statisch 
allokierte Variablen aber schon, was das 
Umstrukturieren/Variantenbildung vereinfacht (gerade auch beim 
Entwickeln/Testen)
- Man kann leicht versehentlich unbemerkt weitere malloc-Aufrufe nach 
der Initialisierungsphase einfügen sodass dann ggf. doch 
Fragmentierungsprobleme oder Verzögerungen auftreten
- Man kann per dynamisch angeforderten Speicher auch nur dynamisch 
initialisieren, während statische Variablen mit fixer Initialisierung 
versehen werden kann, die dann vom Startup-Code sehr effizient 
automatisch vom Flash in den RAM kopiert wird

Ich finde
1
Filter<8> filter1;
2
Filter<4> filter2;
wunderbar simpel vom Handling her, es funktioniert einfach und 
effizient, und man kriegt "gratis" vom Linker die Aufsummierung/Prüfung 
des Speicherverbrauchs, mit Zusammenfassung im Map-File und schicker 
grafischer Darstellung in manchen IDEs. Man kann so natürlich auch 
C-Datenstrukturen "verpacken" und die Speicher-Reservierung in eine 
"Top-Level" C++-Datei verpacken und den Rest des Codes als C belassen.

von Bruno V. (bruno_v)


Lesenswert?

Niklas G. schrieb:
> Warum möchtest du die Interna eines Moduls für andere Komponenten der
> selben Anwendung (!) geheim halten?
Es gibt viele Herangehensweisen an SW. Manche schwören auf Doxygen und 
erzeugen damit wunderbare PDFs zum Preis riesiger 
Interface-Header-Dateien.

Wir halten Header kurz mit minimalen Kommentaren. Da würden in modul.h 
wirklich nur 2-3 Forward-Structs drinstehen (als Hdl) und ein Haufen 
Funktionen für diese Hdl. Insgesamt vielleicht zwei Dutzend Zeilen. 
Keine Implementierungs-Details wie den Aufbau der Strukturen. Sie sind 
trotzdem nicht geheim, da jeder Zugriff hat, auch auf die Libs.

> Der Preis für statische Speicherverwaltung ist dass man die Größen der
> Klassen kennt.
Die Speicherverwaltung ist in meinem Beispiel ja auch statisch. Zum 
Preis eines obskuren und per CT-Assert überwachten Defines.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> Keine Implementierungs-Details wie den Aufbau der Strukturen.

Wenn es dir um die Übersicht geht, kannst du ja auch zwei Header machen; 
einen mit der Forward-Deklaration, einen mit der kompletten Definition 
des structs. Nur die Source-Datei, welche die Instanz tatsächlich anlegt 
sowie die Datei die die Datenstruktur implementiert muss dann den Header 
mit der ganzen Definition inkludieren.

Bruno V. schrieb:
> Zum
> Preis eines obskuren und per CT-Assert überwachten Defines.

widerspricht ein obskures Makro nicht ein bisschen der Idee die Datei so 
übersichtlich wie möglich zu halten?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Alfons H. schrieb:
> Nun wäre die Frage ob man bei der Initialisierung nicht den Speicher für
> den Filter mittels malloc im Heap anlegen könnte. Diese Speicher würden
> nie wieder freigegeben.

Kann man so machen.  Aber bei

> (sehr) hardwarenahen Softwareentwicklung

gibt es auch Ansätze, die weniger Resourcen benötigen wie zum Beispiel 
alloca:  Den Speichen in main per alloca allokieren und die 
Speicheradresse ans Modul übergeben per Funktionsargument oder globaler 
Variable.

alloca benötigt weniger Resourcen als malloc weil:

1) alloca legt den Speicher auf dem Stack an, Allokation besteht also 
nur aus einer Manipulation des Stackzeigers statt einer komplexen 
Routine wie bei malloc.  alloca spart also Programmspeicher.

2) alloca allokiert weniger Speicher als malloc, weil malloc noch etwas 
Platz für Verwaltungsinformationen braucht wie z.B. Größe des 
allokierten Bereichs, Listenverwaltung, etc.  alloca spart also etwas 
RAM.

3) alloca spart auch Laufzeit, weil es wesentlich einfacher ist als 
malloc.

Die Einschränkung ist natürlich, dass die Lebensdauer eines mit alloca 
allokierten Objekts dem einer lokalen Variable entspricht.  Weil main 
nie verlassen wird und das Objekt nie freigegeben wird ist das aber kein 
Problem.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Alfons H. schrieb:
> da malloc bei hardwarenahen Entwicklern ja doch den Ruf hat etwas
> "Böses", was man auf keinen Fall verwendet, zu sein scheint.

Genauso wie goto.

In kleinen embedded Systemen gibt's halt das Problem, dass es für ein 
fehlgeschlagenes malloc keine sinnvolle Fehlerbehandlung gibt.
Soll ein ABS-System dann nur auf drei Rädern bremsen, oder so?

Entweder man weiss vorher, wieviel Speicher man benötigt, oder das 
Design ist fehlerhaft by Design.

Oliver

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Johann L. schrieb:
> Die Einschränkung ist natürlich, dass die Lebensdauer eines mit alloca
> allokierten Objekts dem einer lokalen Variable entspricht.

Was soll alloca dann überhaupt bringen gegenüber einer lokalen 
Variablen?
Alloca hätte nur dann einen Vorteil, wenn die Länge zur Compilezeit noch 
nicht feststeht und z.B. erst über ein Kommando übergeben wird.
Dafür muß man aber die Startadresse und die Länge immer zusätzlich 
mitführen.
Eine Variable ist dagegen dem Compiler bekannt, ihr Name ist die 
Startdaresse und sizeof() ihre Länge.

von Bruno V. (bruno_v)


Lesenswert?

Peter D. schrieb:
> Was soll alloca dann überhaupt bringen gegenüber einer lokalen
> Variablen?
alloca (oder "Aufrufer stellt bereit") ermöglicht es, einen Speicher zur 
Laufzeit einem von n Modulen zu geben. Z.B. abhängig von einem 
Dipschalter Modul A oder Modul B (die unterschiedliche Strukturen 
verwenden)

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> alloca (oder "Aufrufer stellt bereit") ermöglicht es, einen Speicher zur
> Laufzeit einem von n Modulen zu geben. Z.B. abhängig von einem
> Dipschalter Modul A oder Modul B (die unterschiedliche Strukturen
> verwenden)

Das kann man auch genauso gut mit einem statisch allokierten Array 
machen, das man dann dem jeweils aktiven Modul übergibt. Oder einfach 
per union.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Johann L. schrieb:
>> Die Einschränkung ist natürlich, dass die Lebensdauer eines mit alloca
>> allokierten Objekts dem einer lokalen Variable entspricht.
>
> Was soll alloca dann überhaupt bringen gegenüber einer lokalen
> Variablen?

Semantisch gleich aber die Syntax ist etwas anders.  Mit alloca:
1
#include <alloca.h>
2
3
int main (void)
4
{
5
    void *memory = alloca (get_alloc_size());
6
    ...
7
}

Ohne alloca:
1
int main (void)
2
{
3
    char memory[get_alloc_size()];
4
    ...
5
}

> Alloca hätte nur dann einen Vorteil, wenn die Länge zur Compilezeit noch
> nicht feststeht und z.B. erst über ein Kommando übergeben wird.

Das geht auch mit einer Variablen, siehe obiger Code.  Allerdings kann 
es sein, dass neuere C-Standards rumzicken, weil die Größe nicht bekannt 
ist.

> Dafür muss man aber die Startadresse und die Länge immer zusätzlich
> mitführen.

Die Länge ist dem Modul ja bekannt.  Oder man kann sie übergeben als 
get_alloc_size() mit alloca oder auch als sizeof(memory) im 2ten 
Beispiel.

> Eine Variable ist dagegen dem Compiler bekannt, ihr Name ist die
> Startdaresse und sizeof() ihre Länge.

Bringt aber nix. Zumindest dann nicht, wenn der Speicher wie oben in 
main allokiert wird, weil im Modul kann man ja kein sizeof verwenden. 
sizeof funktioniert ja nur in main.

Einfachste Lösung ist get_alloc_size() zu übergeben oder im Modul 
aufzurufen. Ist immer noch weitaus billiger als malloc.

Eine denkbare Bremse wäre hingegen, wenn z.B. malloc langsames externes 
RAM allokiert und man nicht genug (schnelles) internes RAM dafür hat. 
Bei vielen kleinen embedded Systemen wird aber nur eine RAM-Art 
verwendet.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Bruno V. schrieb:
>> alloca (oder "Aufrufer stellt bereit") ermöglicht es, einen Speicher zur
>> Laufzeit einem von n Modulen zu geben. Z.B. abhängig von einem
>> Dipschalter Modul A oder Modul B (die unterschiedliche Strukturen
>> verwenden)
>
> Das kann man auch genauso gut mit einem statisch allokierten Array
> machen, das man dann dem jeweils aktiven Modul übergibt.

Bei einem statischen Array muss die Größe zur Compilezeit vorliegen. 
Bei alloca ist das nicht der Fall.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Bei einem statischen Array muss die Größe zur Compilezeit vorliegen.

Aber das hat dann nichts mit der dynamischen Umschaltung zwischen zwei 
Modulen zu tun. Es sind zwei orthogonale Probleme:
- Größe zur Laufzeit bekannt
- Zwischen 2 Modulen umschalten

Erstes kann man mit malloc, alloca, variable-length-arrays lösen. 
Zweites mit union oder Anlegen eines ausreichend großen Arrays. alloca 
ist also nicht, wie von die gesagt, die Lösung des 2. Problems.

Nur wenn beide Probleme gleichzeitig auftreten kann man alloca in 
Kombination mit der dynamischen Umschaltung nutzen, oder auch eine der 
Alternativen; die Vorteile von alloca haben nichts mit der dynamischen 
Umschaltung zu tun.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Johann L. schrieb:
>> Bei einem statischen Array muss die Größe zur Compilezeit vorliegen.
>
> Aber das hat dann nichts mit der dynamischen Umschaltung zwischen zwei
> Modulen zu tun. Es sind zwei orthogonale Probleme:
> - Größe zur Laufzeit bekannt
> - Zwischen 2 Modulen umschalten
>
> Erstes kann man mit malloc, alloca, variable-length-arrays lösen.
> Zweites mit union oder Anlegen eines ausreichend großen Arrays. alloca
> ist also nicht, wie von die gesagt, die Lösung des 2. Problems.

Ist dann einfach:
1
void *memory = alloca (MAX (get_mem1_size(), get_mem2_size()));
wenn die Module des Speicher exklusiv nutzen.

Beispiel wäre eine Game-App, die mehrere Spiele ausführen kann. Da läuft 
immer nur 1 Spiel, und daher kommen sich mehrere Spiele -- die ja immer 
zeitlich nachenander ausgeführt werden -- nicht in die Quere.

Ginge auch mit einer Union.  Was weniger schön ist, weil alle lokalen 
Datenstrukturen global sichtbar sein müssen, damit main sie sieht.

Je nach verwendeter Toolchain gibt es auch Insellösungen, die keine 
globale Union und kein explizites Allokieren beötogen.  Zum Beispiel 
könnte man mit avr-gcc in allen Teilmodulen, die den exklusiven Speicher 
verwenden wollen:
1
extern memory1_t memory1 __asm("__heap_start"); // in Modul 1
2
extern memory2_t memory2 __asm("__heap_start"); // in Modul 2

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Ist dann einfach:void *memory = alloca (MAX (get_mem1_size(),
> get_mem2_size()));
> wenn die Module des Speicher exklusiv nutzen.

Und warum nicht:
1
char memory [MAX (get_mem1_size(), get_mem2_size())];
oder
1
static char memory [MAX (get_mem1_size(), get_mem2_size())];
oder
1
char* memory = malloc (MAX (get_mem1_size(), get_mem2_size()));

Welchen Vorteil hat alloca hier, denn es nicht hätte, wenn es nur 
ein Modul gäbe?

Johann L. schrieb:
> Zum Beispiel
> könnte man mit avr-gcc in allen Teilmodulen, die den exklusiven Speicher
> verwenden wollen:

Wenn man schon die GNU-Tools voraussetzt, kann man auch ein OVERLAY im 
Linkerscript definieren für die einzelnen Module, dann prüft immerhin 
der Linker ob der Speicher reicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Johann L. schrieb:
>> Ist dann einfach:void *memory = alloca (MAX (get_mem1_size(),
>> get_mem2_size()));
>> wenn die Module des Speicher exklusiv nutzen.
>
> Und warum nicht:
1
> char memory [MAX (get_mem1_size(), get_mem2_size())];
> oder
1
> static char memory [MAX (get_mem1_size(), get_mem2_size())];
> oder
1
> char* memory = malloc (MAX (get_mem1_size(), get_mem2_size()));
>
> Welchen Vorteil hat alloca hier, den es nicht hätte, wenn es nur
> ein Modul gäbe?

1) Objekte im Static Storage brauchen eine zur Compilezeit bekannte 
Größe in C.

2) alloca braucht weniger RAM, weniger Programmspeicher und weniger 
Laufzeit, wie bereits hier ausgeführt: 
Beitrag "Re: Speicherverwaltung bei modularer Echtzeitsoftware"

Ich hatte auch schon erwähnt, dass neuere C-Versionen VLAs nicht mögen.

> Johann L. schrieb:
>> Zum Beispiel
>> könnte man mit avr-gcc in allen Teilmodulen, die den exklusiven
>> Speicher verwenden wollen:
>
> Wenn man schon die GNU-Tools voraussetzt, kann man auch ein OVERLAY im
> Linkerscript definieren für die einzelnen Module, dann prüft immerhin
> der Linker ob der Speicher reicht.

Viele Anwender bevorzugen Lösungen, die kein eigenes Linker-Skript 
brauchen. Aber wer's mag...

Wobei sich der wirkliche RAM-Verbrauch erst zur Laufzeit ergibt.  Zur 
Abschätzung hilf dann z.B.

https://avrdudes.github.io/avr-libc/avr-libc-user-manual-2.3.0/group__util__ram__usage.html

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> 1) Objekte im Static Storage brauchen eine zur Compilezeit bekannte
> Größe in C.

Das hat nichts damit zu tun, wenn man zwischen mehreren Modulen 
umschalten möchte.

> 2) alloca braucht weniger RAM, weniger Programmspeicher und weniger
> Laufzeit, wie bereits hier ausgeführt:
> Beitrag "Re: Speicherverwaltung bei modularer Echtzeitsoftware"

Das hat nichts damit zu tun, wenn man zwischen mehreren Modulen 
umschalten möchte.

> Ich hatte auch schon erwähnt, dass neuere C-Versionen VLAs nicht mögen.

Das hat nichts damit zu tun, wenn man zwischen mehreren Modulen 
umschalten möchte.

Johann L. schrieb:
> Wobei sich der wirkliche RAM-Verbrauch erst zur Laufzeit ergibt.

Bei Verwendung von OVERLAY, der C++ -Lösung, globalen/statischen 
Variablen nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Das hat nichts damit zu tun, wenn man zwischen mehreren Modulen
> umschalten möchte.

Warum fragst du dann danach?

Bei Verwendung von alloca ist es egal, wie viele Module man verwendet.

> Johann L. schrieb:
>> Wobei sich der wirkliche RAM-Verbrauch erst zur Laufzeit ergibt.
>
> Bei Verwendung von OVERLAY, der C++ -Lösung, globalen/statischen
> Variablen nicht.

Und wie will dein OVERLAY den Stack-Verbrauch erkennen?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Warum fragst du dann danach?

Weil hier das Gegenteil behauptet wurde, dem ich widersprochen hatte, 
dem du wiederum widersprochen hattest:

Bruno V. schrieb:
> alloca (oder "Aufrufer stellt bereit") ermöglicht es, einen Speicher zur
> Laufzeit einem von n Modulen zu geben. Z.B. abhängig von einem
> Dipschalter Modul A oder Modul B (die unterschiedliche Strukturen
> verwenden)

Sinnlose Diskussion wenn man meine Beiträge nicht genau liest.

Johann L. schrieb:
> Und wie will dein OVERLAY den Stack-Verbrauch erkennen?

Es ist nicht "mein" OVERLAY, und den Stackverbrauch kann man anderweitig 
ermitteln, außer man benutzt alloca() oder variable length arrays...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Sinnlose Diskussion wenn man meine Beiträge nicht genau liest.

Der TO sucht eine Lösung in C.

Beitrag "Re: Speicherverwaltung bei modularer Echtzeitsoftware"

von Tobias (code_red)


Lesenswert?

Ein ganzer Thread über Echtzeit-Bedingungen und kein einziges Mal wird 
ein Echt-Zeit Betriebssystem erwähnt.

Ist schon manchmal faszinierend hier...

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Der TO sucht eine Lösung in C.

Richtig. Manche Leser könnten aber an der C++ Lösung interessiert sein.

Tobias schrieb:
> Ein ganzer Thread über Echtzeit-Bedingungen und kein einziges Mal wird
> ein Echt-Zeit Betriebssystem erwähn

Wie soll ein Echtzeitbetriebssystem die Speicherverwaltung echtzeitfähig 
machen, wenn sie das zuvor nicht war?

von Bruno V. (bruno_v)


Lesenswert?

Niklas G. schrieb:
> Sinnlose Diskussion wenn man meine Beiträge nicht genau liest.

Naja, Deine Lösung "array bereitstellen" habe ich genauso als Beispiel 
auskodiert und explizit empfohlen. Da haben wir keinen Dissens.

Und dass es zwei unterschiedliche Aufgaben sind (Größer zur Compilezeit 
und Aufteilung auf 2 Module), ist Konsens.

Dissens gibt es quasi nur bei der Union:
 * weil sie 2 (oder mehr) unabhängige Module verbinden
 * gar nicht funktionieren, wenn es nicht 1 von 2 sondern z.B. 3-7 von 
12 Modulen sind. Ein typisches Modul wäre die Instanz eines 
Uart-Treibers, wo abhängig vom Gegenüber 1..10k Fifo gewünscht werden 
und wo es 1..5 verschiedene geben kann.

Und dabei, dass alloca ein bequemer Standardweg ist und ein paar kleine 
Vorteile gegenüber meiner manuellen Verteilung hat.

(Dein implizit oben angebrachtes Argument, dass ab x kB oder MB alles 
egal ist und man darauf nicht mehr achten muss, ist in meinem Umfeld 
nicht stichhaltig. Die Probleme bleiben die gleichen)

: Bearbeitet durch User
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.