Forum: Compiler & IDEs malloc rausoptimieren


von Jürgen S. (jsachs)


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):
1
class buffer
2
{
3
  buffer(int size);
4
  uint8_t *buf;
5
}
6
7
buffer::buffer(int size);  // Die Groesse des Puffers festlegen
8
{
9
  buf = malloc(size);  // <<--- Das soll ersetzt werden
10
}
11
12
class inbuffer(70);
13
class outbuffer(30);
14
15
int main(void)
16
{
17
  // do something
18
}

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

von tuppes (Gast)


Lesenswert?

Im Prinzip so:
1
#include "iostream"
2
3
typedef unsigned char uint8_t;
4
5
template <unsigned int size>
6
class buffer
7
{
8
public:
9
  uint8_t buf[size];
10
};
11
12
buffer<70> inbuffer;
13
buffer<30> outbuffer;
14
15
int main(void)
16
{
17
  std::cout << outbuffer.buf;
18
  return 0;
19
}

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

von Jürgen S. (jsachs)


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 ?

von tuppes (Gast)


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.

von Johann L. (gjlayde) Benutzerseite


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/Variable-Length.html#Variable-Length

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

Johann

von (prx) A. K. (prx)


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

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


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.

von tuppes (Gast)


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.

von Stefan E. (sternst)


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.

von (prx) A. K. (prx)


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.

von Stefan E. (sternst)


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.

von (prx) A. K. (prx)


Lesenswert?

Illustration zum obigen Ansatz:
1
class UARTbase {
2
public:
3
    ....
4
    ....
5
    ....
6
7
    void interrupt ();        // interrupt handler
8
9
protected:
10
    UARTbase (int no, uint8_t *rxp, int rxsz, uint8_t *txp, int txsz)
11
      { init(no, rxp, rxsz, txp, txsz); }
12
13
private:
14
    uint16_t    rxsize, txsize;
15
    volatile uint8_t  *rxbuf, *txbuf;
16
    ....
17
};
18
19
//-----------------------------------------------------------------------------
20
21
template<int no, int rxsz, int txsz>
22
class UARTc : public UARTbase {
23
public:
24
    UARTc () : UARTbase(no, rxb, rxsz, txb, txsz) {}
25
protected:
26
    uint8_t rxb[rxsz?rxsz:1], txb[txsz?txsz:1];
27
private:
28
    static void irq() IRQ_FUNCTION;
29
};

von (prx) A. K. (prx)


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.

von Stefan E. (sternst)


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

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


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.

von Jürgen S. (jsachs)


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):
1
#define MALLOC_SIZE (IN_BUF_SIZE+OUT_BUF_SIZE+XYZ_BUF_SIZE)
2
static uint8_t malloc_buffer[MALLOC_SIZE];
3
4
uint8_t *myMalloc(uint8_t size)
5
{
6
  static uint8_t *start = malloc_buffer;
7
  register uint8_t *t;
8
  t = start;
9
  //TODO Fehlerprüfung ob genug platz
10
  start += size;
11
  return t;
12
}

so mal als schnellschuss.

Danke schon mal für die Tipps.
Juergen

von Karl H. (kbuchegg)


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?

von Jürgen S. (jsachs)


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.

von Karl H. (kbuchegg)


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.

von Michael A. (micha54)


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

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.