mikrocontroller.net

Forum: Compiler & IDEs malloc rausoptimieren


Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich habe mir eine Applikation für den AVR in c++ (gcc) geschrieben, die 
prima läuft.
Jetzt möchte ich etwas ram und flash sparen, da kommt mir spontan die 
malloc in den Kopf. Ich reserviere nur speicher, gebe Ihn nie frei.

Am besten etwas Beispielcode (Pseudo):
class buffer
{
  buffer(int size);
  uint8_t *buf;
}

buffer::buffer(int size);  // Die Groesse des Puffers festlegen
{
  buf = malloc(size);  // <<--- Das soll ersetzt werden
}

class inbuffer(70);
class outbuffer(30);

int main(void)
{
  // do something
}


Der Speicher wird nie Freigegeben. Ich brauche das nur weil ich das oft 
benutze und die größe des Puffers festlegen möchte.

Dafür verbraucht malloc unnötig Flash und Ram für die Verwaltung.
Eine ne Idee wie das C++ konform geht.

Danke
Juergen

Autor: tuppes (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Im Prinzip so:
#include "iostream"

typedef unsigned char uint8_t;

template <unsigned int size>
class buffer
{
public:
  uint8_t buf[size];
};

buffer<70> inbuffer;
buffer<30> outbuffer;

int main(void)
{
  std::cout << outbuffer.buf;
  return 0;
}

Das mit dem std::cout ist nur ein Beispiel, das demonstriert, dass man 
auf den Puffer zugreifen kann. Public-Member sind natürlich nicht schön. 
Wie du das ausgestaltest, ist dir überlassen

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok,

das mit dem template ist klar soweit.
Aber brauche ich dann nicht mehr speicher weil 2 Classen erzeugt werden 
mit allen Funktionen die dazu gehören ? Der Constructor ist ja nicht 
alles :-)

Ich habe hier keine Ahnung wie das implementiert ist.
Ansonsten hilft vermutlich nur ein test ?

Autor: tuppes (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du wolltest ausdrücklich eine C++-Lösung. In C hätte ich sowas hier 
vorgeschlagen:

[c]
char inbuffer[30];
char outbuffer[70];
int main (void)
{
// ....
}

Der Konstruktor der Template-Klasse da oben ist der Default-Konstruktor, 
der macht gar nichts, andere Methoden gibts auch keine. Das Objekt 
besteht nur aus dem Speicherbereich und aus nichts sonst. Ist also wohl 
die Frage, welche Runtime-Lib (C oder C++) das mit weniger Aufwand 
hinkriegt.

Trotzdem: Wenn der Buffer auch nur minimal gemanagt sein soll (z.B. 
Schutz gegen Overrun), rate ich zur C++-Implementierung, weil du da den 
Index-Operator ([]) überladen und mit einem assert (index < size) 
sichern kannst.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn sich dir Größe erst zur Laufzeit ergibt -- etwa wenn am Anfang von 
main per Kommunikation die Größe eines anzulegenden Puffers erwartet 
wird oder in einer Übertragungsfunktion, kann man in dieser Funktion 
alloca verwenden:

   http://gcc.gnu.org/onlinedocs/gcc-4.3.3/gcc/Variab...

Der Platz wird auf dem Stack angelegt und hat den Gültigkeitsbereich 
einer in der aufrufenden Funktion angelegten auto-Variablen.

Johann

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Normale Basisklasse mit allen übrigen Daten und Funktionen definieren. 
Einen Zeiger auf den Puffer darin speichern und per Parameter des 
Konstrukturs initialisieren.

Davon wird dann ein Template abgeleitet, in dem nur der Puffer definiert 
und dessen Adresse und Länge an den Basisklassenkonstruktor übergeben 
wird. Des Rest erbt man von der Basisklasse, folglich entsteht kein 
duplizierter Code.

Auf ähnliche Art kriegt man auch Klassen für I/O-Module für mehrere 
Schnittstellen mit Interrupts in den Griff. Indem der Interrupt-Handler 
im Template steht (static wg. stackframe) und darin den gemeinsamen 
Handler der Basisklasse aufruft (normale member funktion).

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Man kann übrigens für solche Fälle auch ein Primitiv-malloc()
schreiben.  Das nimmt sich einen großen Pool Speicher (kann man
ja als uint8_t malloc_pool[4096] oder so vereinbaren) und
alloziert daraus mit einem einfachen Zeiger, der immer hochgezählt
wird, munter drauflos.  Die Kosten dafür sind minimal.

Autor: tuppes (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Das nimmt sich einen großen Pool Speicher (kann man
> ja als uint8_t malloc_pool[4096] oder so vereinbaren)

Ich dachte, es soll RAM gespart werden, lies mal die Ursprungsfrage.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
tuppes wrote:
>> Das nimmt sich einen großen Pool Speicher (kann man
>> ja als uint8_t malloc_pool[4096] oder so vereinbaren)
>
> Ich dachte, es soll RAM gespart werden, lies mal die Ursprungsfrage.

Der Speicher für den/die Puffer muss so oder so vorhanden sein. Der 
Vorschlag von Jörg benötigt lediglich zusätzlich den Platz für einen 
einzigen Pointer zur Verwaltung. Ich halte dieses Trivial-Malloc für die 
sinnvollste Möglichkeit.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
An malloc und dem Stack misfällt mir die fehlende 
Speichernutzungskontrolle. Ich ziehe es daher vor, Speicher statisch zu 
allozieren. Sieht man sofort, wieviel RAM weg ist bzw. für den Stack 
übrig bleibt.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
A. K. wrote:
> An malloc und dem Stack misfällt mir die fehlende
> Speichernutzungskontrolle. Ich ziehe es daher vor, Speicher statisch zu
> allozieren. Sieht man sofort, wieviel RAM weg ist bzw. für den Stack
> übrig bleibt.

Wenn man das Trivial-Malloc (wie vorgeschlagen) auf einem Array 
operieren lässt, sieht man es doch auch sofort. Und so wie ich es 
verstanden habe, weiß er zur Compile-Zeit, was er an Puffer braucht, und 
kann daher dieses Array entsprechend dimensionieren.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Illustration zum obigen Ansatz:
class UARTbase {
public:
    ....
    ....
    ....

    void interrupt ();        // interrupt handler

protected:
    UARTbase (int no, uint8_t *rxp, int rxsz, uint8_t *txp, int txsz)
      { init(no, rxp, rxsz, txp, txsz); }

private:
    uint16_t    rxsize, txsize;
    volatile uint8_t  *rxbuf, *txbuf;
    ....
};

//-----------------------------------------------------------------------------

template<int no, int rxsz, int txsz>
class UARTc : public UARTbase {
public:
    UARTc () : UARTbase(no, rxb, rxsz, txb, txsz) {}
protected:
    uint8_t rxb[rxsz?rxsz:1], txb[txsz?txsz:1];
private:
    static void irq() IRQ_FUNCTION;
};

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan Ernst wrote:

> Wenn man das Trivial-Malloc (wie vorgeschlagen) auf einem Array
> operieren lässt, sieht man es doch auch sofort.

Nur muss man dummerweise die Grösse dieses Arrays irgendwo festlegen. 
Also zusammenrechnen, was welcher malloc verbraucht. Bei modularer 
Programmierung arg fehlerträchtig. Und liefert (wenn falsch) gern mal 
Laufzeitfehler wo Fehler zu Übersetzungszeit sinnvoller sind. Ich neige 
dazu, solche Jobs dem Compiler und Linker zu überlassen. Wenns geht. Und 
es geht ja.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
A. K. wrote:

> Nur muss man dummerweise die Grösse dieses Arrays irgendwo festlegen.
> Also zusammenrechnen, was welcher malloc verbraucht. Bei modularer
> Programmierung arg fehlerträchtig. Und liefert (wenn falsch) gern mal
> Laufzeitfehler wo Fehler zu Übersetzungszeit sinnvoller sind.

Ja, auch nicht von der Hand zu weisen.

Das Trivial-Malloc hätte halt den Vorteil, dass der komplette 
Originalcode erhalten bleiben kann, und der hatte schließlich schon den 
Status "läuft prima".

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
A. K. wrote:

> Nur muss man dummerweise die Grösse dieses Arrays irgendwo festlegen.
> Also zusammenrechnen, was welcher malloc verbraucht.

Man kann auch stattdessen einfach eine Obergrenze für die maximale
Speicheradresse vorgeben und fängt dann (wie das normale malloc)
hinter dem Ende des .bss an zu allozieren.  In der Debugversion
wird dann statt der Rückgabe von NULL irgendeine auffällige Fehler-
meldung produziert (das können wild blinkende LEDs sein oder was
auch immer die Applikation so bietet).  Da die Allozierung sowieso
am Anfang erfolgt, sieht man das dann sofort.

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Speicher sparen war auf das in diesem Fall unnötige Ram für die 
Speicherverwaltung und auf den unnötigen Flash für free(). Wobei das der 
Compiler vermutlich schon einspart. Wenn ich dann noch ein wenig sparen 
kann durch eine "schmalere" malloc soll es mir recht sein.

Die Idee von Jörg ist gar nicht schlecht mit dem statischen Array.
Die Größen der einzelnen Puffer sind zur compilezeit bekannt und als 
Konstante vorhanden.
Ich könnte also ein 
"malloc_puffer[IN_BUF_SIZE+OUT_BUF_SIZE+XYZ_BUF_SIZE]" machen.
Vorteil wäre hier auch ich würde zur compile(link)zeit sehen wie viel 
RAM ich verbrauche.

Muss ich mir nur ne einfache Malloc überlegen...

@Jörg: Das mit der blinkenden LED hab ich in den Constructoren schon 
verbaut :-)

Habe ich bei folgendem was übersehen (Pseudocode aus dem Stegreif):
#define MALLOC_SIZE (IN_BUF_SIZE+OUT_BUF_SIZE+XYZ_BUF_SIZE)
static uint8_t malloc_buffer[MALLOC_SIZE];

uint8_t *myMalloc(uint8_t size)
{
  static uint8_t *start = malloc_buffer;
  register uint8_t *t;
  t = start;
  //TODO Fehlerprüfung ob genug platz
  start += size;
  return t;
}

so mal als schnellschuss.

Danke schon mal für die Tipps.
Juergen

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Die Größen der einzelnen Puffer sind zur compilezeit bekannt und als
> Konstante vorhanden.

Und aus welchem Grund willst du dann die Arrays nicht ganz einfach 
statisch anlegen?

Autor: Jürgen Sachs (jsachs)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es ist ein Object für Pufferhandling mit allen notwendigen Funktionen. 
Einzig die Größe des Puffers kann beim Start variabel gemacht werden.

Bei einem Objekt ist eben alles schön versteckt und gekapselt. Wäre es 
ein normales "C", hätte man viele Funktionen und Arrays, die irgendwie 
zusammengehören. Einfach schöner :-)

Ich werde mir auch so eine minimalloc ohne free basteln. Das legt dann 
ein Statisches Array an. So sieht man auch gleich wie viel Ram man 
verbraucht beim Compilieren. Das dynamische ist bei so wenig Speicher 
einfach Lotterie.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jürgen Sachs wrote:
> Es ist ein Object für Pufferhandling mit allen notwendigen Funktionen.
> Einzig die Größe des Puffers kann beim Start variabel gemacht werden.

Wann nun?
Beim Start (des Programmes), oder beim Compilieren?

> Bei einem Objekt ist eben alles schön versteckt und gekapselt. Wäre es
> ein normales "C", hätte man viele Funktionen und Arrays, die irgendwie
> zusammengehören. Einfach schöner :-)

Ist im Grunde auch nicht anders als in C++.
Auf dieser Ebene betrachtet und wenn man auf dieser Ebene halt macht, 
hat C++ ledlich 'syntactic sugar' zu bieten.

> Das dynamische ist bei so wenig Speicher
> einfach Lotterie.

Sehe ich auch so. Daher: Buffer statisch (also Globale) anlegen. Dann 
sagt dir der Compiler/Linker nach dem Erstellen des Programms wieviel 
Speicher du verbraucht hast.

Autor: Michael Appelt (micha54)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich habe für C eine pragmatische Lösung:

// prototype
typedef struct
{
  uint8_t size;
  uint8_t buf(1);
} buffer;

// memory allocation compiletime dynamic, runtime fixed
#define BUFFER(s) struct \
{\
  uint8_t size;\
  unit8_t buf(s);\
}

// besser sogar noch so, mit initialisierung
#define BUFFER(s,n) struct \
{\
  uint8_t size;\
  unit8_t buf(s);\
} n = {s}

// zum Zugriff müssen generische BUFFER mit cast versehen werden
#define BUFFCAST(b) (buffer *) b

// Nutzung it
BUFFER(10) inbuf = {10};
BUFFER(50) outbuf = {30};

oder auch

BUFFER(10, inbuf);
BUFFER(50, outbuf);

Gruß,
Michael

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]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [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.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

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