Hallo,
Für einen ARM Cortex M0+ plus (Samd21), benötigt der Compiler eine
Funktion die sich _sbrk() nennt. Sonst erhalte ich folgende
Fehlermeldung:
1
Severity Code Description Project File Line
2
Error undefined reference to `_sbrk' blah C:\MyFiles\blah\Debug\sbrkr.c 1
3
Error recipe for target 'blah.elf' failed blah C:\MyFiles\blah \Debug\Makefile 193
4
Error ld returned 1 exit status ipc191 collect2.exe 0
Ich habe festgestellt, dass ich diesen Fehler nur bekommen, wenn ich
zuweisungen zu arrays, bzw structuren initialisiere. Wenn ich folgendes
z.b. vor der main() schreibe, dann funktioniert alles wie gewohnt.
void* _sbrk() { return 0; }
Was bewirkt diese funktion und weshalb benötigt der gcc diese unbedingt?
Genauer gesagt:
Üblicherweise ist der Speicher, der nach dem Laden eines Programms noch
frei ist, in Gedanken in zwei Bereiche geteilt: vom oberen Ende abwärts
wird er als Stack genutzt, von unten her (also oberhalb Programmcode,
statischer und globaler Variablen) als Heap, also für malloc()-Aufrufe.
Anfangs weiß noch niemand, wieweit der Stack von oben genutzt wird und
wieweit der Heap von unten her genutzt wird und wo sich die beiden
vielleicht treffen werden, wenn es zuwenig Speicher gibt.
Deshalb wird einfach eine Trennlinie relativ weit unten gezogen
("break"), und wenn der Heap nicht reicht, wird die Grenze nach oben
verschoben dem Stack entgegen.
brk() setzt diese Grenze auf einen Wert, sbrk() verschiebt die Grenze um
ein Stück (und ruft dafür brk() auf).
Wenn man also fleißig malloc() macht, wird dafür intern von Zeit zu Zeit
mit sbrk() die Grenze nach oben verschoben.
Solange die Summe aus Heap+Stack nicht größer wird als der verfügbare
Speicher, passt das also - ohne sich vorher festlegen zu müssen, wer von
den beiden wieviel bekommt.
Wenn du jetzt dafür eine Funktion reinschreibst, die nur NULL liefert,
kommst du zwar über das Linken hinweg. Sinnvoll laufen wird das Programm
aber eher nicht...
Stattdessen musst du wohl eine Lib dazu linken, in der sbrk() sinnvoll
definiert ist. Oder auf dynamischen Speicher verzichten.
Dank Klaus, das war sehr verständlich.
Es war die funktion sprintf/snprintf die ich benutz hatte um
debugausgaben Formatiert zu generieren. Nun habe ich mich entschieden
diesen ganzen mißt zu ersetzen, durch einfachere strcat funktionieren.
Ich möchte alles was mit stdio zutun hat und malloc, auf einem µC nicht
nutzen.
Wie machst du stringverarbeitung auf einem µC?
Würde hier schon c++ sinn machen? oder resourcenverschwenung?
adc schrieb:> Wie machst du stringverarbeitung auf einem µC?> Würde hier schon c++ sinn machen? oder resourcenverschwenung?
Was µC angeht, kratze ich hier nur am unteren Ende rum; bevor hier ein
falscher Eindruck entsteht.
Geschickt gemacht, lohnt sich C++ sicher (auch wenn das hier nicht
mehrheitsfähig ist).
Aber nicht, weil es Speicher sparen würde - das kann es natürlich nicht.
Aber man kann vieles einfach und eleganter ausdrücken. Und vieles aus
C++ muß man sich verkneifen wegen Resourcen.
Wenn es knapp ist (AVR eher), fährt man manchmal besser mit eigenen
Strings; C++-Strings auf jeden Fall nicht.
Danke Klaus,
ja ich denke auch auf nem Cortex M0+ hat man mindestens 16k speicher. Da
kann man schon drüber nachdenken C++ zu nehmen. Aber die macht der
gewohnheit :D
Ich mag C und wünschte mit ein paar mehr features, z.b. einfachen
Classenstatments.
adc schrieb:> Wie machst du stringverarbeitung auf einem µC?
Mit den Funktionen aus stdio.h. Ein einfaches printf() braucht zwar
einen funktionierenden Heap, aber keinen großen. Ob ich da nun
kurzzeitig 200 Bytes auf dem Stack brauche oder einem separatem Heap,
ist am Ende auch egal.
> Würde hier schon c++ sinn machen? oder resourcenverschwenung?
Mit C++ kann man recht viel auf den Compiler auslagern und bekommt
optimierte Implementationen für Standardaufgaben. Dafür wird der Code
schnell schlechter lesbar, wenn man nicht aufpasst und man muss deutlich
mehr wissen, um das ordentlich nutzen zu können.
In C muss man halt mehr selbst machen, dafür passiert im Hintergrund
weniger Magie. Ich nutze meist C, auch auf dem PC (oder dort gleich
Python).
adc schrieb:> Nun habe ich mich entschieden diesen ganzen mißt zu ersetzen, durch> einfachere strcat funktionieren.> Ich möchte alles was mit stdio zutun hat und malloc, auf einem µC nicht> nutzen.
Diese Ansicht ist auf einem 8051 oder ATtiny13 sicher sehr sinnvoll.
Aber µC != µC. Heutzutage kann ein "µC" größer und leistungsfähiger sein
als die PDP-11, von der UNIX (und mit ihr stdio sowie all der
C-Bibliothekskram) stammt. Klar kann man auch alles wieder selbst von
vorn schreiben, einschließlich seiner eigenen, neu geschriebenen Bugs.
Man kann aber auch gut und gern bewährtes benutzen. Auf einem ARM (auch
wenn es „nur“ ein Cortex-M0 ist) gehört stdio durchaus zu dem, was man
nutzen kann, wenn man es braucht. Die Chancen, dass du es selbst besser
hin bekommst sind nicht allzu groß.
adc schrieb:> Für einen ARM Cortex M0+ plus (Samd21), benötigt der Compiler eine> Funktion die sich _sbrk() nennt.
Hier eine Referenzimplementation von _sbrk für STM32, sollte auch für
Deinen µC funktionieren:
1
caddr_t_sbrk(intincrement)
2
{
3
externcharendasm("end");
4
registerchar*pStackasm("sp");
5
staticchar*s_pHeapEnd;
6
7
if(!s_pHeapEnd)
8
{
9
s_pHeapEnd=&end;
10
}
11
12
if(s_pHeapEnd+increment>pStack)
13
{
14
return(caddr_t)-1;
15
}
16
17
char*pOldHeapEnd=s_pHeapEnd;
18
s_pHeapEnd+=increment;
19
return(caddr_t)pOldHeapEnd;
20
}
Scheue Dich nicht, printf() & Co auf einem Cortex-M zu nutzen, auch
malloc() & Co ist da überhaupt kein Problem. Nutze ich für STM32
regelmäßig und macht keine Probleme.
Frank M. schrieb:> sollte auch für Deinen µC funktionieren:
Nicht unbedingt. Ich glaube mich zu erinnern, dass die
Atmel-Linkerscripte den Stack nicht ans Ende des RAMs legen sondern in
eine abgegrenzte Region davor. Dann klappt diese Kollisionsberechnung
mit dem Stackpointer nicht mehr.
Leider ziemlich viel Wildwuchs hier im ARM-Bereich. :-(
Jörg W. schrieb:> Ich glaube mich zu erinnern, dass die> Atmel-Linkerscripte den Stack nicht ans Ende des RAMs legen sondern in> eine abgegrenzte Region davor. Dann klappt diese Kollisionsberechnung> mit dem Stackpointer nicht mehr.
Beides passt nicht zu einem Cortex-M0+ (das + ist entscheidend). Der
kann die Grenzen von Heap und Stack dank MPU per Hardware überwachen.
Das sollte in keinem Programm fehlen, damit programmiert es sich viel
entspannter.
Unabhängig davon: wenn Atmel ein unbrauchbares Linker Script schreibt
muss ich das doch nicht benutzen?
Und noch unabhängiger: wozu braucht euer printf() heap? Das soll
gefälligst direkt auf stdout ausgeben. Ich dachte, wir waren uns einig,
dass man malloc() freiwillig nicht benutzt?
Bauform B. schrieb:> Beides passt nicht zu einem Cortex-M0+ (das + ist entscheidend). Der> kann die Grenzen von Heap und Stack dank MPU per Hardware überwachen.
MPU ist meiner Erinnerung nach eine Option (auch bei anderen Cortexen),
und ich glaube mich weiter zu erinnern, dass der SAMD21 diese Option
nicht hat.
> Unabhängig davon: wenn Atmel ein unbrauchbares Linker Script schreibt> muss ich das doch nicht benutzen?
Muss man sicher nicht (ich bin auch kein Fan von diesen dedizierten
Stackregionen, noch dazu „irgendwo in der Mitte“ – die Variante „ganz
unten“ hat ja zumindest noch etwas Charme). Aber wenn der TE hier schon
nach sbrk fragt, dann vermute ich, dass er mit einem custom linker
script (der unweigerlich einen custom startup code nach sich zieht)
reichlich überfordert ist.
> Und noch unabhängiger: wozu braucht euer printf() heap?
Weil stdio üblicherweise gepuffert arbeitet, und (wenn man nichts
anderes angibt) der Puffer via malloc() alloziert wird.
> Das soll> gefälligst direkt auf stdout ausgeben.
Das eine hat mit dem anderen nichts zu tun.
> Ich dachte, wir waren uns einig,> dass man malloc() freiwillig nicht benutzt?Du kannst dir ja da einig sein – aber was soll das „wir“?
Auch für dich nochmal als Erinnerung: heutige µCs können oft genug eine
PDP-11 in die Tasche stecken, sowohl von der Performance als auch vom
Speicherausbau. Auf der ist aber malloc() entstanden.
Jörg W. schrieb:> wozu braucht euer printf() heap?>> Weil stdio üblicherweise gepuffert arbeitet>> Das soll gefälligst direkt auf stdout ausgeben.> Das eine hat mit dem anderen nichts zu tun.
ja, ok. Aber muss man sich auf einem Cortex-M0+ so streng an den
Standard halten? Ich finde, Optimierung fängt damit an, ein schlankes
printf() zu benutzen.
>> Ich dachte, wir waren uns einig,>> dass man malloc() freiwillig nicht benutzt?> Du kannst dir ja da einig sein – aber was soll das „wir“?
wir Forumsbesucher? Dass malloc() böse ist hab' ich hier schon öfter
gelesen.
> Auch für dich nochmal als Erinnerung: heutige µCs können oft genug eine> PDP-11 in die Tasche stecken, sowohl von der Performance als auch vom> Speicherausbau. Auf der ist aber malloc() entstanden.
Das ist ja auch ein richtiger Rechner, jedenfalls wenn du RSX-11 drauf
tust. Und natürlich funktioniert malloc() auch mit weniger Speicher.
Bauform B. schrieb:> wir Forumsbesucher? Dass malloc() böse ist hab' ich hier schon öfter> gelesen.
Wieso soll das böse sein?
Ist doch nur eins der vielen Dinge, die mitgeliefert werden, wenn man
mit C oder C++ spielt.
Pointer und goto sind doch auch nicht böse, nur weil sie etwas Sorgfalt
und Disziplin erfordern. Diese Dinge werden erst durch Irrtümer und
Schlampereien böse. Egal ob auf µC oder fetten PC.
Was aber richtig ist:
> Jedes vermiedene malloc() ist ein gutes malloc()> Jedes vermeidbare malloc() ist ein böses malloc()
Merke:
Die beiden Ansagen haben eine Hysterese, der Bereich der notwendigen in
dem malloc() Sinn macht und keinen Schaden anrichtet.
Bauform B. schrieb:> wir Forumsbesucher? Dass malloc() böse ist hab' ich hier schon öfter> gelesen.
malloc() ist nicht generell böse. Das trifft wirklich nur auf die
kleinsten µCs zu, weil dort eventuell die Menge an
Verwaltungsinformation die tatsächlich benötigte Speichergröße
übersteigt. Das trifft auf kleinere AVRs zu, okay. Wir sprechen hier
aber über einen Cortex-M, das ist eine andere Hausnummer.
Die einzge Gefahr ist, dass Du durch die die vielfache(!) Anwendung der
Kombination malloc(), free() und realloc() den Speicher fragmentierst.
Aber das schaffst Du nicht mit einer Handvoll von
malloc/realloc-Aufrufen, das müssen schon ein ein paar hundert oder
tausend sein.
Wenn es nur um printf geht, macht der vielleicht ein malloc. Später
vielleicht für einen größeren Speicher noch einen zweiten. Vorher gibt
er aber den ersten Block wieder frei oder macht einen realloc - das
heißt man beginnt wieder bei 0. Hier kann überhaupt keine
Fragementierung auftreten.
Fragmentierung passiert nur, wenn an vielen verschiedenen(!) Orten im
Code immer wieder realloc-free-malloc-Aufrufe geschehen, die so
ungünstig verteilt sind, dass sie die vormals freigegebenen Bereiche
nicht mehr ausfüllen können - einfach, weil sie immer mehr und mehr
brauchen. So eine Situation ist sehr unwahrscheinlich und lässt sich
durch geschickte Anwendung auch vermeiden. Zum Beispiel dadurch, dass
man nicht exakt diejenige Menge allokiert, die man aktuell benötigt,
sondern eher mehr, weil man eventuell weiß, dass diese Funktion später
mehr benötigen wird.
Man arbeitet also mit Chunks in definierten Blockgrößen. Und schon
entspannt man dadurch die Situation, weil sich die Anzahl der
malloc-Calls sich dramatisch verringern.
Mal ein einfaches Beispiel, überspitzt beschrieben: Man programmiert
einen Editor. Hier wäre es Unsinn, für jedes eingegebene Zeichen seinen
Puffer-Speicher um ein einzelnes Zeichen zu vergrößern, wenn der User
einen Buchstaben tippt. Stattdessen allokiert man direkt 2 KB mehr, um
die nächsten 2048 Zeichen abzulegen - auch wenn der User gerade nur ein
Zeichen tippt. Für die nächsten 2047 Zeichen sind dann keine
realloc-Calls mehr notwendig:
Genauso arbeitet auch stdio: Es "denkt" in Blockgrößen - nicht unbedingt
in konkreten nach aktuellem Bedarf ausgerichteten Größen.
Nochmal:
> Dass malloc() böse ist hab' ich hier schon öfter gelesen.
Generell gesehen ist das ein Märchen, das muss man immer im Kontext
sehen. Okay, für einen ATTiny akzeptiere ich diese Aussage. Für einen
Cortex-M nicht.
Von daher: malloc ist nicht generell böse. Man muss nur wissen, wie man
damit umgeht.
Arduino Fanboy D. schrieb:> Was aber richtig ist:>> Jedes vermiedene malloc() ist ein gutes malloc()>> Jedes vermeidbare malloc() ist ein böses malloc()
tja, wie macht man es richtig?
Brauche ich temporär in einer Funktion Speicher kann ich malloc nutzen
und wieder frei geben, aber wenn ich die Funktion verlasse und nicht
freigegeben habe ist irgendwann der Speicher alle.
Gebe ich den Speicher in der Funktion frei ist das Ergebnis welches in
dem Speicher lag auch weg.
Reserviere ich für die Funktion Speicher global ist der Speicher immer
verbraucht, auch wenn die Funktion vielleicht nie aufgerufen wird.
Die Funktionen aber jedes mal einzukopieren oder nicht, wahlweise ist
auch nicht so toll.
Muss man also für eigene Funktionen eine eigene LIB erstellen, oder jede
eigene Funktion mit Rückgabepointer erstellen für malloc damit nach dem
Benutzen der Funktion mit Auswertung der Rückgabe danach freigegeben
werden kann?
Wie verhindert man die Fragmentierung vom Speicher?
Eine Garbage Collection kenne ich auf µC (noch) nicht.
Joachim B. schrieb:> Muss man also für eigene Funktionen eine eigene LIB erstellen, oder jede> eigene Funktion mit Rückgabepointer erstellen für malloc damit nach dem> Benutzen der Funktion mit Auswertung der Rückgabe danach freigegeben> werden kann?
Man braucht keine Lib dafür, man braucht nur zwei static-Variablen, die
den letzten Rückgabewert von realloc/malloc und die zuletzt allokierte
Größe ablegt. Zusammen mit dem Rezept aus meinem Vorgänger-Beitrag, der
sich knapp mit Deinem überschnitten hat, ist es ein Leichtes, seinen
Speicher zu "verwalten". Das ist trivial.
> Wie verhindert man die Fragmentierung vom Speicher?
Siehe meinen Beitrag über Deinem.
> Eine Garbage Collection kenne ich auf µC (noch) nicht.
Garbage Collection ist mit Standard-C prinzipiell nicht möglich. Diese
müsste nämlich alle durch malloc/realloc gewonnenen Pointer, die Du
irgendwo im Code verteilt hast, aktualisieren.
Frank M. schrieb:> Siehe meinen Beitrag über Deinem.
danke
> Garbage Collection ist mit Standard-C prinzipiell nicht möglich
ja leider, vermisse ich immer noch....
wenn ich mal wenige Byte brauche mal eben 2K zu reservieren macht auf
vielen AVR keinen Sinn, immer die ganzen Blockpointer belegt/frei
mitzuschleppen artet ja in Arbeit aus!
Ein Glück das die SRAM auch auf µC immer größer werden (ESP32 vs.
PET2001), da verschiebt sich das Problem und zurück zu ASM und jeden
Speichertrick ausnutzen will nicht wirklich JEDER, ich kenne hier nur
einen ;)
Aber schon irre was die Programmierer mit ASM und nur 8KB früher
programmiert hatten!
Joachim B. schrieb:> Brauche ich temporär in einer Funktion Speicher kann ich malloc nutzen> und wieder frei geben, aber wenn ich die Funktion verlasse und nicht> freigegeben habe ist irgendwann der Speicher alle.> Gebe ich den Speicher in der Funktion frei ist das Ergebnis welches in> dem Speicher lag auch weg.> Reserviere ich für die Funktion Speicher global ist der Speicher immer> verbraucht, auch wenn die Funktion vielleicht nie aufgerufen wird.
Kommt halt drauf an, was du im Einzelfall brauchst.
Falls der Speicher nur in der Funktion benötigt wird, ist alloca() das
Mittel der Wahl (falls deine C-lib das kennt).
Dann wird der Speicher gar nicht vom Heap, sondern vom Stack abgezwackt
und beim Verlassen automatisch freigegeben.
Wenn der Speicher bis zum Aufrufer durchgereicht werden soll, muß der
ihn irgendwann freigeben. Evtl. kann man schon vor dem Aufruf
abschätzen, wie groß der Speicher nötig ist. Dann ist es eleganter, wenn
der Aufrufer vorher selber allokiert und wieder freigibt (ggf.
automatisch mit alloca()).
Man muß in C* nunmal den Überblick haben, hilft nix.
(C++ könnte da in vielen Punkten helfen, aber das will man ja nicht :-)
Z.B. kann man sich Objekte bauen, in denen ein Zeiger steckt und die
über ihre Methoden bei Bedarf Speicher holen, verlängern oder was auch
immer. Im Destruktor wird dann freigegeben. Der Aufrufer macht sich so
ein Objekt oder die aufgerufene Funktion erzeugt es, und wenn der
Aufrufer es nicht mehr braucht wird automatisch freigegeben. Also etwa
wie eine einfache Version von std::string.)
Frank M. schrieb:>> Eine Garbage Collection kenne ich auf µC (noch) nicht.>> Garbage Collection ist mit Standard-C prinzipiell nicht möglich. Diese> müsste nämlich alle durch malloc/realloc gewonnenen Pointer, die Du> irgendwo im Code verteilt hast, aktualisieren.
Nicht nur die, sondern alle Pointer die irgendwo in den jeweils
allokierten Block zeigen, z.B. wenn man einen Pointer nutzt, um durch
ein von malloc kommendes Array zu iterieren.
Joachim B. schrieb:> Frank M. schrieb:>> Siehe meinen Beitrag über Deinem.>> danke>>> Garbage Collection ist mit Standard-C prinzipiell nicht möglich>> ja leider, vermisse ich immer noch....>> wenn ich mal wenige Byte brauche mal eben 2K zu reservieren macht auf> vielen AVR keinen Sinn, immer die ganzen Blockpointer belegt/frei> mitzuschleppen artet ja in Arbeit aus!
Wie soll da Garbage Collection helfen?
Rolf M. schrieb:> Wie soll da Garbage Collection helfen?
keine Ahnung, ist wie Kuchen backen und Popobacken, 2 Dinge die nichts
gemein haben.
Ich sage ja ich kenne die optimale Lösung noch nicht!
Ich weiss auch nicht wie das früher gemacht wurde, der AtariST mit TOS
1.0 warf einfach nur Bomben (nach 128 malloc), mit TOS 1.4 haben sie nur
den Puffer vergrößert und die Bomben kamen später (nach 1024 malloc).
Selbst heute unter Linux meint mein Qnap NAS "sie haben lange nicht mehr
neu gestartet......"
Das Thema scheint immer noch brandaktuell zu sein!
Der Witz mit dem "reboot" oder "Neustart" scheint kein Witz zu sein.
Joachim B. schrieb:> Wie verhindert man die Fragmentierung vom Speicher?
Verhindern kannst du sie nicht.
Man kann mit einer gewissen Wahrscheinlichkeit erreichen, dass sie kein
Problem wird, wenn es immer ausreichend Speicherreserve gibt und wenn
die Anforderungen in ihrer Größe tatsächlich zufällig sind. Dann wirken
da statistische Effekte, die das auf Dauer ausgleichen. (Das ist wie die
10%ige Reserve in der Dateisystembelegung, die mit UFS damals eingeführt
worden ist. Die wurde nicht etwa eingeführt, damit root immer noch was
mehr bekommt, sondern damit die Fragmentierung statistisch kein Problem
wird. Der 10-%-Wert wurde dabei empirisch ermittelt.)
Ich hab's schon einige Male hier geschrieben: wenn man tatsächlich
Probleme zu lösen hat, die dynamische (nicht vorhersagbare!)
Speicheranforderungen beinhalten (das bisschen printf hier gehört nicht
dazu – das alloziert einmal einen Puffer und benutzt den dann immer
wieder), dann wird man in aller Regel mit einem gut ausgetesteten und
designten malloc() allemal die Anforderungen am besten erfüllen. Jede
selbst gestrickte Speicherverwaltung produziert in diesem Fall mit hoher
Wahrscheinlichkeit mehr Bugs und ist schneller an der Erschöpfung des
verfügbaren Speichers angelangt. Für ein wirklich dynamisches Problem
benötigt man ohnehin immer einen Plan B: "Was kann und muss ich tun,
wenn ich (temporär) mehr Speicheranforderungen als verfügbaren Speicher
habe?"
Jörg W. schrieb:> Joachim B. schrieb:>> Wie verhindert man die Fragmentierung vom Speicher?>> Verhindern kannst du sie nicht.
Ein Garbage Collector könnte sie tatsächlich verhindern, bzw. nebenher
den Speicher defragmentieren. Da er sowieso alle Stellen kennen muss,
die einen dynamischen Speicherbereich referenzieren, hat er dadurch auch
die Möglichkeit, diesen an eine andere Stelle im Speicher zu verschieben
und alle Referenzen anzupassen.
Klaus W. schrieb:> (C++ könnte da in vielen Punkten helfen, aber das will man ja nicht :-)> Z.B. kann man sich Objekte bauen, in denen ein Zeiger steckt und die> über ihre Methoden bei Bedarf Speicher holen, verlängern oder was auch> immer. Im Destruktor wird dann freigegeben. Der Aufrufer macht sich so> ein Objekt oder die aufgerufene Funktion erzeugt es, und wenn der> Aufrufer es nicht mehr braucht wird automatisch freigegeben. Also etwa> wie eine einfache Version von std::string.)
In C++ gibt es die "Smart Pointer", welche das im Ansatz, können.
Leider nicht für AVR, allerdings für ARM, ESP und die anderen 32 und
64Bit µC, schon.
Die smarten Pointer haben einen Referenzzähler im Bauch, und wenn der
auf Null geht, wird der Speicher freigegeben.
Zumindest das Problem "Memory leak" lässt sich damit erfolgreich
erschlagen.
Rolf M. schrieb:> Ein Garbage Collector könnte sie tatsächlich verhindern,
Ja!
Allerdings kennen C und C++ das Prinzip der RAW Pointer.
Sprachen mit GC verzichten eben auf diese RAW Pointer, diese arbeiten
ausschließlich mit Referenzen.
Rolf M. schrieb:> Ein Garbage Collector könnte sie tatsächlich verhindern, bzw. nebenher> den Speicher defragmentieren.
OK, ja. Gibt's im Prinzip auch für C, als Add-on.
Ich habe in vielen Jahren in den Projekten kein malloc gebraucht. Ich
habe auch keine eigene Speicherverwaltung gebaut bis auf zwei Ausnahmen,
da war das Vorgabe vom Kunden. Es ist bei uns schlicht verboten malloc
zu benutzen. Ich persönlich halte es nicht pauschal für so böse aber ich
habe es auch nie wirklich vermisst. Und die Schilderung von Jörg weiter
oben wie man das defragmentieren verhindert.. Klar geht. Aber ich finde
es einen unnötigen Aufwand, die Lösung ist nicht wasserdicht und wenn es
dann ein Problem gibt mit Speicherüberlauf wegen Defragmentierung dann
ist das unter Umständen sehr schwer zu finden. Alles in allem für mich
eine ungünstige Lösung. Komischer Weise habe ich malloc auch nicht
vermisst.
Und den Vorschlag alloca... den Zweck der Funktion habe ich bisher nicht
verstanden. Die Funktion holt den Speicher vom Stack, dann kann ich auch
gleich ein Array in der Funktion anlegen als Variable. Da geht mir der
Sinn der Funktion alloca verloren. Ok, nur falls tatsächlich die
gebrauchte Größe vorher nicht bekannt ist, dann vielleicht. Aber wenn
die größte zu allokierende Größe bekannt ist, dann ein Array in der
Größe. Muss ja eh auf den Stack passen.
900ss D. schrieb:> Und den Vorschlag alloca... den Zweck der Funktion habe ich bisher nicht> verstanden. Die Funktion holt den Speicher vom Stack, dann kann ich auch> gleich ein Array in der Funktion anlegen als Variable.
Alloca wird verwendet, wenn du eben die Größe nicht vorher kennst. Der
Vorteil gegenüber malloc ist daß der Speicher beim Verlassen der
Funktion automatisch freigegeben wird, d.h. kein free() Aufruf notwendig
ist.
Und natürlich auch keine Fragmentierung des Speichers auftritt.
Der Nachteil ist daß der Speicher vom Stack geholt wird, der in manchen
Architekturen arg limitiert ist.
900ss D. schrieb:> und wenn es dann ein Problem gibt mit Speicherüberlauf wegen> Defragmentierung dann ist das unter Umständen sehr schwer zu finden
Dann gibt malloc() eine 0 zurück. Ich schrieb ja: wenn man tatsächlich
ein dynamisches Problem hat, muss man immer mit der Situation zurecht
kommen, dass der Speicher ggf. mal nicht ausreicht – Fragmentierung hin
oder her.
Gerader Ast schrieb:> Alloca wird verwendet, wenn du eben die Größe nicht vorher kennst.
Geht inzwischen genauso mit "variable-length arrays" (VLA) – was
letztlich nichts anderes ist als alloca(). Hauptnachteil: alles, was auf
dem Stack läuft (egal, ob alloca(), statisch vordefiniert oder VLA, hat
keine Prüfung auf Kollision (es sei denn, man hat eine MMU, die den
Stack monitoren kann *). Insofern ist das für große Datenmengen die
allerschlechteste Empfehlung, denn ein normales malloc() kann zumindest
versuchen zu prüfen, dass es nicht mit dem Rest kollidiert.
*) Oder hilfsweise diesen Konstrukt, bei dem der Stack beim Cortex-M
ganz am Anfang des RAMs liegt, sodass ein Überlauf nach unten einen
Hardfault bringt.
Wie siehts denn aus wenn man (v)fprintf nutzt?
Da lässt sich ein eigenbau Filepointer übergeben.
In dem trägt man dann kein Buffer sein, sondern seine Ausgabefunktion
und stellt das auf inline.
1
gui_font_monopage::gui_font_monopage():
2
m_framebuffer(NULL){
3
4
#if GUI_FONT_PRINTF_ENABLE
5
m_file._r=0;// nothing to read here
6
m_file._w=0xFFFFFFFF;// simulate "unlimited" bc it wraps
7
m_file._flags=__SNBF|__SWR;// unbuffered, write
8
m_file._file=-1;// no unix files here
9
m_file._bf._base=m_outbuf;// the buffer (at least 1 byte, if !NULL)
10
m_file._bf._size=sizeof(m_outbuf);// the buffer (at least 1 byte, if !NULL)
11
m_file._lbfsize=-sizeof(m_outbuf);// 0 or -_bf._size, for inline putc
12
m_file._read=NULL;// no read
13
m_file._write=charout;// our write function to the frame buffer
14
m_file._cookie=this;// cookie passed to io functions
Mw E. schrieb:> Da lässt sich ein eigenbau Filepointer übergeben.
Ist meist keine gute Idee, denn du greifst auf undokumentierte Interna
der Implementierung zu. Wenn, dann eher mit setvbuf() einen eigenen
Puffer übergeben.
Jörg W. schrieb:> Ist meist keine gute Idee. Wenn, dann eher mit setvbuf() einen eigenen> Puffer übergeben.
Das wär auch ne Idee.
Das setzt aber auch nen Filepounter und fprintf vorraus.
https://www.cplusplus.com/reference/cstdio/setvbuf/
Hat aber auch Charme, ich hab mich mit grep zur Struktur durchgehangelt
und das dann so gefüllt wies da in den Kommentaren stand.
Bei meinem Beispiel wird Text auf ein Pixeldisplay ausgegeben mit der
Bequemlichkeit von printf.
Daher brauchts ja auch ne eigene Ausgabefunktion.
Nicht alle prints sollen ja aufs Display.
Zum setzen der Ausgabefunktion hab ich jetzt in der functions list vom
Link oben nix gefunden?
Mw E. schrieb:> Hat aber auch Charme, ich hab mich mit grep zur Struktur durchgehangelt> und das dann so gefüllt wies da in den Kommentaren stand.
Kann sich mit der nächsten Library-Version aber schon wieder ändern.
Sind halt Interna.
> Bei meinem Beispiel wird Text auf ein Pixeldisplay ausgegeben mit der> Bequemlichkeit von printf.
Ja, und?
> Daher brauchts ja auch ne eigene Ausgabefunktion.
Klar. Machen auch andere.
> Nicht alle prints sollen ja aufs Display.
Dafür wurde bei fprintf() das Argument FILE * erfunden.
Da es hier um Cortex-M (und damit wahrscheinlich um newlib) geht: der
generische Weg ist es hier, dass man auch noch _open() implementiert.
Dann kann man sowas wie
1
FILE*display=fopen("display","w");
2
(void)freopen("uart","w",stdout);
3
4
// ...
5
printf("Hello world!\n");// => uart
6
fprintf(display,"Welcome!");// => display
machen. Natürlich muss man sich intern merken, welcher Filedescriptor
(Rückgabewert von _open()) mit welchem Gerät assoziiert wurde.
Vorteil: alles dabei ist komplett dokumentiert und verhält sich
standardmäßig.
(Genau so haben wir es übrigens in einem kommerziellen Projekt
gehandhabt, wobei die Gerätenamen intern noch dynamisch auf Treiber
abgebildet werden und alle nicht ein Gerät bezeichnenden Namen auf die
SD-Karte zugreifen.)
Jörg W. schrieb:> Dann gibt malloc() eine 0 zurück
Ja sicher aber diesen Fall "darf" es nicht geben. Wenn gleich er
natürlich angefangen werd muss. Wie gesagt, bei uns nicht erlaubt. Es
muss vorher beim Design entsprechend ausgelegt werden.
Je Situation ist alloca() vielleicht hilfreich aber ich hab es auch noch
nicht vermisst.
900ss D. schrieb:>> Dann gibt malloc() eine 0 zurück>> Ja sicher aber diesen Fall "darf" es nicht geben.
Wenn du ein dynamisches Problem hast (nur da hat malloc() im
ursprünglichen Sinne ja Sinn), dann kannst du das nicht per se
verhindern. Du kannst nur die Wahrscheinlichkeit reduzieren, dass er
auftritt.
Ich rede jetzt von solchen Aufgaben, bei denen du schlicht nicht
(sicher) vorhersagen kannst, wann welche Anforderung reinkommt.
Jörg W. schrieb:> Da es hier um Cortex-M (und damit wahrscheinlich um newlib) geht
Das hätte google dem TO sagen, und auch passende Lösungen zu seinem
Problem liefern können, wäre es nicht so unzuverlässig und andauernd
kaputt.
Es hätte zwar auch noch RTFM gegeben, aber das kann man heutzutage ja
wirklich nicht mehr verlangen.
Aber egal, google geht hier wieder, und findet z.B. das hier:
https://interrupt.memfault.com/blog/boostrapping-libc-with-newlib
Oliver
Wobei man vorsichtig sein muss damit, im C-Standard definierte
Funktionen durch eigene zu ersetzen. Mit -fhosted (der default) darf der
Compiler eingebautes Wissen über die Standardbibliothek besitzen und
anwenden; u.U. greift er dann gar nicht auf die entsprechenden
Funktionen der Bibliothek zu.
Typisches Beispiel:
1
printf("Foo!\n");
ruft kein printf() auf sondern stattdessen:
1
puts("Foo!");
(Man beachte das weggelassene '\n'.)
Solche Optimierungen können natürlich durchaus wünschenswert sein, bspw.
bringt:
1
i=strlen("Foo!");
dann die Konstante 4, die der Compiler direkt so benutzen kann.
Wenn man all diese Optimierungen nicht will, kann man mit -ffreestanding
compilieren, aber dann müsste man eben für den vorherigen Ausdruck die
umständlich zu lesende Form
Normalerweise möchte man das auch nicht global ausschalten, weil das
zumindest im Falle von gcc wirklich einen großen Teil der
Standardfunktionen umfasst. Da kann schon einiges an
Optimierungspotenzial verloren gehen.