Forum: Mikrocontroller und Digitale Elektronik Flash und RAM-Bedarf anzeigen


von Dennis S. (eltio)


Lesenswert?

Hallo zusammen,

ich habe nach dem Kompilieren mit der GNU Toolchain eine fertige 
ELF-Datei.

- Wie kann ich jetzt den tatsächlichen Speicherbedarf herausfinden? Ist 
das "size"-Tool zum Anzeigen von "text", "data" und "bss" dafür 
ausreichend?
- Gibt es irgendwelche "Sonderfälle" bei denen ich falsche Ergebnisse 
bekomme?
- Wie ist der Zusammenhang zum Linker-Script? Das dürfte ja keinen 
Einfluss auf den tatsächlichen Speicherbedarf haben.

Gruß
Dennis

von Stefan F. (Gast)


Lesenswert?

Das size Tool zeigt leider nicht an, wieviel dynamischen Speicher dein 
Programm belegt. Das kannst du nur zur Laufzeit ungefähr herausfinden. 
Zum Beispiel so:

Belege mit malloc wiederholt 128 Bytes RAM, bis es nicht mehr geht. Die 
Summe davon ist der (ungefähr) verfügbare Speicher.

Der Haken ist: Es kann sein, dass kein 128 Byte Block am Stück mehr 
verfügbar ist, aber noch jede Menge kleinere Blöcke. Andererseits macht 
es keinen Sinn, 1-byte Blöcke anzutesten, weil dann die Verwaltungsdaten 
dazu mehr Platz beanspruchen, als die Nutzdaten.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dennis S. schrieb:
> - Wie kann ich jetzt den tatsächlichen Speicherbedarf herausfinden? Ist
> das "size"-Tool zum Anzeigen von "text", "data" und "bss" dafür
> ausreichend?

Es ist - soweit ich weiß - die einzige Möglichkeit. Hier wird aber nur 
der statische Speicherplatzberbrauch angezeigt.

> - Gibt es irgendwelche "Sonderfälle" bei denen ich falsche Ergebnisse
> bekomme?

Der Speicherplatz von lokalen Variablen auf dem Stack kann nicht 
berechnet werden, da dies ja vom Laufzeitverhalten des Programms 
abhängt.

Ein:
1
void meine_super_funktion ()
2
{
3
   char mein_array[10000];
4
   ...
5
}

wird einen ATTiny auf jeden Fall erst zur Laufzeit zum Platzen bringen. 
Das wird Dir nicht vorher angezeigt!

Bei:
1
void meine_super_funktion ()
2
{
3
   static char mein_array[10000];
4
   ...
5
}

jedoch schon. Aber nun hast Du eventuell auch das Verhalten Deiner 
Funktion geändert....

von Dennis S. (eltio)


Lesenswert?

@Stefan us
Interessanter Ansatz, danke dafür!

@Frank M.
Okay, aber beim Flash-Verbrauch sollte ich doch mit dem Tool auf der 
sicheren Seite sein oder?
Mir ist nicht ganz klar, warum die lokalen Variablen nicht 
berücksichtigt werden. Ist das darin begründet, dass die entsprechende 
Funktion unter Umständen gar nicht ausgeführt wird?
Mir ist klar, dass bei jedem Funktionsaufruf die Variable an einer 
anderen Stelle liegen kann, aber dass sie in einer bestimmten Größe 
existieren muss, ist doch sicher?

Gruß
Dennis

Edit: Hmm... bei rekursiven Aufrufen wäre die Variable natürlich öfter 
vorhanden...

von Karl H. (kbuchegg)


Lesenswert?

Dennis S. schrieb:

> Mir ist nicht ganz klar, warum die lokalen Variablen nicht
> berücksichtigt werden. Ist das darin begründet, dass die entsprechende
> Funktion unter Umständen gar nicht ausgeführt wird?
> Mir ist klar, dass bei jedem Funktionsaufruf die Variable an einer
> anderen Stelle liegen kann, aber dass sie in einer bestimmten Größe
> existieren muss, ist doch sicher?
1
void foo()
2
{
3
  char tmp[20];
4
}
5
6
void bar()
7
{
8
  char tmp[80];
9
}
10
11
int main()
12
{
13
  if( PINC & ( 1 << 0 )
14
    foo();
15
  else
16
    bar();
17
}

woher soll der Compiler wissen, ob der Pin auf 0 ist oder nicht?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dennis S. schrieb:
> Edit: Hmm... bei rekursiven Aufrufen wäre die Variable natürlich öfter
> vorhanden...

Eben, der Speicher wird bei lokalen Variablen dynamisch verwendet. In 
der Regel wird dafür ein Stack benutzt, auf dem sich die lokalen 
Variablen stapeln. Beim Funktionsaufruf kommen neue Variablen auf den 
Stapel, beim Return werden sie wieder vom Stapel genommen.

Genauer gesagt, ändert sich nur der Stackpointer nach oben und nach 
unten. Aus diesem Grunde ist der Speicherinhalt einer lokalen Variablen 
auch anfangs nicht definiert. Da kann alles mögliche drin stehen. 
Deshalb sollte man lokale Variablen tunlichst auch initialisieren, bevor 
man auf sie lesend zugreift.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> woher soll der Compiler wissen, ob der Pin auf 0 ist oder nicht?

Das ist das eine.
Das andere ist, dass Funktionen ja je nach Programmlogik in 
verschiedenen Hierarchieebenen aufgerufen werden können. Ein 
entsprechendes Tool müsste also hergehen und im Grunde den kompletten 
Call-Graphen aufbauen um festzustellen, welches der maximale 
Speicherverbrauch im Worst Case ist. Egal ob der tatsächlich eintritt 
oder nicht, bzw. ob der überhaupt bestimmbar ist oder nicht (Rekursion).

Weiters gibt es dann noch die Komplikation von Interrupt Routinen. Je 
nachdem zu welchem Zeitpunkt der Interrupt auftritt oder nicht, hast du 
eine andere Grundlast am Stack, zu der sich dann noch der 
Speicherverbrauch der ISR samt der darin aufgerufenen Funktionen 
addiert. Das kann sich allerdings wieder ändern, wenn zwischendurch im 
Hauptprogramm die Interrupts gesperrt sind bzw. freigegeben werden. D.h. 
in so eine Fall müsste man dann tatsächlich die Programmausführung 
simulieren, mit allen nur denkbaren Möglichkeiten inklusive mehrerer 
vorhandener ISR die sich im schlimmsten Programmierfall auch noch 
gegenseitig unterbrechen könnten, um eine realistische aussagefähige 
Zahl zu bekommen.

All das macht aber avr-size nicht.

von Dennis S. (eltio)


Lesenswert?

Karl Heinz schrieb:
> woher soll der Compiler wissen, ob der Pin auf 0 ist oder nicht?

Naja, schon klar! Allerdings impliziert das dann auch, dass der Compiler 
IMMER solche Dinge wegoptimiert, unabhängig von der Einstellung -O?

von Karl H. (kbuchegg)


Lesenswert?

Dennis S. schrieb:
> Karl Heinz schrieb:
>> woher soll der Compiler wissen, ob der Pin auf 0 ist oder nicht?
>
> Naja, schon klar! Allerdings impliziert das dann auch, dass der Compiler
> IMMER solche Dinge wegoptimiert, unabhängig von der Einstellung -O?

Genau aus diesem Grund hab ich einen Portpin für das Beispiel genommen. 
Denn über dessen Wert weiss der Compiler nichts. Was aber nicht bedeuten 
muss, dass der Pin jemals eine 1 liefert. Du könntest ja den Pin auch 
extern auf GND verdrahtet haben.

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Hi,
sollte das Entwicklungssystem unserer Wahl keine entsprechenden 
Werkzeuge an Bord haben, kann man z.B. folgenden Ansatz wählen:

Nachdem uns der Linker den statischen Speicherbedarf gemeldet hat, 
können wir zur Laufzeit den dynamischen Speicherbedarf ermitteln.

Vom Linkerskript wissen wir, wo Stack und Heap im RAM liegen.

Z.B. auf dem ARM Cortex des STM32 läuft der Heap von unten hoch, der 
Stack kommt von oben runter.

Im Startup-Code, weit vor der Main und noch bevor der Compiler seine 
Initialisierung macht, initialisieren wir den Stackbereich mit einem 
Marker (z.B. $A5) und den Heap mit einem anderen Marker (z.B. $AF).

In der main-Loop durchforsten wir nun regelmäßig den Heap von TOP nach 
BOTTOM. Die erste Speicherstelle, die nicht mehr den Marker enthält 
zeigt uns den maximal verwendeten Heap-Speicher an.

Für den Stack machen wir dasselbe umgedreht - von BOTTOM in Richtung 
TOP.
Damit erfahren wir, wie tief der Stack jemals belegt war.

Die damit gewonnenen Erkenntnisse kann man nun z.B. für die Statistik 
anzeigen. Und, wenn bestimmte Limits überschritten werden, wird ein 
Desaster-Recovery angestoßen:
- während der Entwicklung eine Ausgabe (bevorzugt ohne calls ;) ) mit 
anschließender for-ever-Schleife
- in der Produktionsversion eine angemessene Reaktion (dieser Punkt 
erfordert wirklich Fingerspitzengefühl)

Grüße,
 Marcus

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.