Forum: Compiler & IDEs gcc komischer fehler: sbrk()


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von adc (Gast)


Lesenswert?

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?

: Verschoben durch Admin
von pegel (Gast)


Lesenswert?

Das steht sicher irgendwo in den Quellen beschrieben.
Beim ARM STM32:
1
 * @brief _sbrk() allocates memory to the newlib heap and is used by malloc
2
 *        and others from the C library

von Klaus W. (mfgkw)


Lesenswert?

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.

: Bearbeitet durch User
von adc (Gast)


Lesenswert?

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?

von Klaus W. (mfgkw)


Lesenswert?

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.

von adc (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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

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


Lesenswert?

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

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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(int increment)
2
{
3
    extern char end asm("end");
4
    register char * pStack asm("sp");
5
    static char *   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.

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


Lesenswert?

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

von Bauform B. (bauformb)


Lesenswert?

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?

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


Lesenswert?

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.

von Bauform B. (bauformb)


Lesenswert?

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.

von Arduino Fanboy D. (ufuf)


Lesenswert?

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.

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

von Joachim B. (jar)


Lesenswert?

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.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Joachim B. (jar)


Lesenswert?

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!

von Klaus W. (mfgkw)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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?

von Joachim B. (jar)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von Arduino Fanboy D. (ufuf)


Lesenswert?

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.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von 900ss D. (900ss)


Lesenswert?

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.

von Gerader Ast (Gast)


Lesenswert?

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.

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


Lesenswert?

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.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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
15
  #endif
16
}

Aufruf:
1
int gui_font_monopage::printf(uint16_t x_start, uint16_t y_start, font font, const char * format, ... ) {
2
/* bla bla bla SNIP*/
3
  int printed;
4
  va_list args;
5
  va_start(args, format);
6
  printed = vfprintf(&m_file, format, args);
7
  va_end(args);
8
  
9
  return printed;
10
}
11
#endif

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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?

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


Lesenswert?

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

von 900ss D. (900ss)


Lesenswert?

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.

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


Lesenswert?

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.

von Rudolph R. (rudolph)


Lesenswert?

Ich benutze gelegentlich mal das hier mit den ATSAM:
https://github.com/mpaland/printf

von Oliver S. (oliverso)


Lesenswert?

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

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


Lesenswert?

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
1
  i = sizeof("Foo!") - 1;

wählen.

von Rolf M. (rmagnus)


Lesenswert?

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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.