Forum: Mikrocontroller und Digitale Elektronik [C] XC8 verschwendet Speicher


von B. S. (bestucki)


Lesenswert?

Hallo zusammen

Ich habe folgendes Problem:
Wenn ich in einer Funktion ein lokales Array initialisiere, reserviert 
der Compiler dafür statisch Speicher im RAM. Dies auch, wenn ich die 
Funktion nur zum Projekt hinzugefügt habe und sie nirgends aufrufe. Wenn 
ich das Array mit const deklariere, reserviert er Speicher im 
Programmspeicher.

Dies bedeutet: Nach dem Kompilieren habe ich zwar das Array im RAM, aber 
die Funktion dazu nicht im Programmspeicher (weil ich sie ja nicht 
aufrufe). Das ist doch hirnrissig! Warum tut der Compiler sowas? Oder 
ist hier der Linker der Schuldige?
1
void foo void{
2
  uint8_t a;               // RAM wird nicht reserviert
3
  uint8_t b=0;             // RAM wird nicht reserviert
4
5
  uint8_t c[10];           // RAM wird nicht reserviert
6
  uint8_t d[10]={0};       // RAM wird reserviert
7
8
  const uint8_t e[10]={0}; // ROM wird reserviert
9
}

Compiler: XC8 V1.21
IDE: MPLAB V8.92


EDIT: Das Wichtigste fast vergessen: Wie bringe ich den Compiler dazu, 
solchen Unsinn zu unterlassen?

Nachtrag:
Habe gerade im Listing festgestellt, dass der Compiler das Array 
scheinbar so behandelt, als wäre es mit static deklariert worden. 
Anfangs im main werden die Initialwerte in das Array geschrieben.

: Bearbeitet durch User
von Bupf (Gast)


Lesenswert?

Schalt halt man die Optimierung an.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

be stucki schrieb:
> Wenn ich in einer Funktion ein lokales Array initialisiere, reserviert
> der Compiler dafür statisch Speicher im RAM.

Bist Du Dir da sicher? Deine Beispiele --von der /const/-Variante 
abgesehen-- sind allesamt automatische Variablen, die nur während der 
Laufzeit Deiner Funktion existieren, und zwar auf dem Stack.

Daß der Compiler dafür dauerhaft RAM reserviert, wäre sehr merkwürdig.

von Nitrobor (Gast)


Lesenswert?

Und dann sollte man sich auch noch ueberlegen, ob diese Variablen, falls 
man sie denn braucht, mit Null initalisiert sein sollen.

von Peter D. (peda)


Lesenswert?

Rufus Τ. Firefly schrieb:
> und zwar auf dem Stack.

Hängt vom unbekannten CPU-Typ ab.
Z.B. viele PIC haben keinen echten Stack (kein PUSH/POP, SP). Sie können 
nur eine begrenzte Zahl von Returnadressen in einer Art FIFO speichern.
Das macht einem Compiler das Leben sehr schwer. Deshalb gibt es 
C-Compiler für PIC auch noch nicht so lange, wie z.B. für 8051 oder AVR 
und sie haben oft Einschränkungen.

von Meh-meh-mek (Gast)


Lesenswert?

Ein vernuenftiger Compiler schaut eh, dass er soviele Variablen wie 
moeglich in den Registern hat. Der Hardware Stck des PIC ist fuer 
Interrupts.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Meh-meh-mek schrieb:
> Ein vernuenftiger Compiler schaut eh, dass er soviele Variablen wie
> moeglich in den Registern hat.

Ein zehn Byte großes Array?

von Karl H. (kbuchegg)


Lesenswert?

Da hier
>   uint8_t c[10];           // RAM wird nicht reserviert
>   uint8_t d[10]={0};       // RAM wird reserviert

der einzige Unterschied darin besteht, dass das eine initialisiert wird, 
und das andere nicht, denke ich, dass das was du im Speicherverbauch 
siehst in Wirklichkeit nicht das Array selber ist, sondern Speicher der 
benötigt wird um dort die Initialwerte unterzubringen. Der Compiler wird 
das Array zur Laufzeit dergestalt initialisieren, dass er einfach einen 
vordefinierten Speicherbereich, der die Werte enthält, auf das neu 
erzeugte Array 'am Stack' umkopiert.

> Nachtrag:
> Habe gerade im Listing festgestellt, dass der Compiler
> das Array scheinbar so behandelt, als wäre es mit static
> deklariert worden. Anfangs im main werden die Initialwerte
> in das Array geschrieben.

Das kann er mit Sicherheit nicht machen (ausser der Compiler ist schwer 
fehlerhaft). Denn dieses Array muss ja bei jedem Funktionseintritt neu 
initialisiert werden. Aber irgendwo müssen ja auch die Initialwerte 
herkommen.

Wenn ich richtig liege, dann kann man dem Compilerbauer vorwerfen, dass 
er den Fall 'initialisiere alles mit 0' nicht explizit bedacht hat, denn 
der kommt tatsächlich ja nicht so selten vor. Anstelle da Werte 
umzukopieren, hätte es auch genügt, im Array alle Bytes in einer 
Schleife auf 0 zu setzen.

Aber das kannst du ja selber auch machen.
1
void foo void{
2
  uint8_t d[10];
3
4
  memset( d, 0, sizeof(d)/sizeof(*d) );
5
6
  ...
7
}

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

Leute! Der XC8 ist ein C Compiler für 8-bit PICs. Die haben weder 
nennenswert Register noch einen Stack im herkömmlichen Sinn. OK, auf 
PIC18 könnte man sich einen Parameterstack im RAM bauen. Aber wir wissen 
ja nicht, was für ein PIC das ist. Dazu kommt noch, daß der XC8 
vermutlich nur in der lite-Version vorhanden ist, also mit reduzierten 
Optimierungsmöglichkeiten.

Ein benutztes Array muß der Compiler auf jeden Fall irgendwo im RAM 
ablegen (egal ob Stack oder nicht). Und er muß auch eine Kopie der 
Initialwerte im ROM haben.

Der vom TE vorgenommene "Test" ist weltfremd. Kein Schwein interessiert, 
wie gut ein Compiler unbenutzte Variablen wegoptimieren kann. In realen 
Programmen gibt es die nämlich nicht.


XL

Edit: Nachtrag: bzw. wenn es in realen Programmen so viele unbenutzte 
Variablen gibt, daß die wirklich Ärger machen (a'la teuerer Controller 
nötig) dann gehört der Entwickler gefeuert.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Axel Schwenke schrieb:

> Der vom TE vorgenommene "Test" ist weltfremd. Kein Schwein interessiert,
> wie gut ein Compiler unbenutzte Variablen wegoptimieren kann. In realen
> Programmen gibt es die nämlich nicht.

Zustimmung.

Aber der TO hat den Fall
>  reserviert der Compiler dafür statisch Speicher im RAM. Dies auch,
> wenn ich die Funktion nur zum Projekt hinzugefügt habe und sie
> nirgends aufrufe.
und der kommt schon häufiger vor.

Ein durchforsten der Linker-Anweisungen, so dass der unbenutzte 
Funktionen besser rauswirft, könnte auch einiges bewirken.

von B. S. (bestucki)


Lesenswert?

Vielen Dank für die Antworten, hier noch ein paar Infos:

Nitrobor schrieb:
> Und dann sollte man sich auch noch ueberlegen, ob diese Variablen, falls
> man sie denn braucht, mit Null initalisiert sein sollen.
Ich verwende das Array als Lookup-Tabelle und initialisiere auf die 
entsprechenden Werte. Die Initialisation im meinem Eingangspost dient 
nur als Beispiel.

Karl Heinz schrieb:
> denke ich, dass das was du im Speicherverbauch
> siehst in Wirklichkeit nicht das Array selber ist, sondern Speicher der
> benötigt wird um dort die Initialwerte unterzubringen.
Fände ich aber auch merkwürdig, da das Array in meinem Beispiel auf Null 
initialisiert wird und alle PICs den Befehl CLRF (Register löschen) 
besitzen...

Karl Heinz schrieb:
> Denn dieses Array muss ja bei jedem Funktionseintritt neu
> initialisiert werden.
Habe ich nicht explizit nachgeschaut, weil ich die Funktion gar nicht 
aufgerufen habe und somit nicht im Programmspeicher vorhanden ist. 
Schaue heute Abend nochmals genauer nach, was er da wirklich tut.

Karl Heinz schrieb:
> Aber das kannst du ja selber auch machen.
Auf Null initialisieren schon, aber meine Werte müsste ich mühsam per 
Divisions-Gedöhns berechnen, dann ist die Lookup-Table selbst wieder 
hinfällig.

Axel Schwenke schrieb:
> Aber wir wissen
> ja nicht, was für ein PIC das ist.
PIC16F876 (leider). Hat nur einen 8 Level tiefen Stack und der dient nur 
zur Speicherung der Rücksprungaddresse.

Axel Schwenke schrieb:
> Dazu kommt noch, daß der XC8
> vermutlich nur in der lite-Version vorhanden ist
Exakt.


Ich werde heute Abend noch ein paar weitere Beispiele kompilieren und 
dann den Code sowie das erzeugte Listing posten.

von Chris (Gast)


Lesenswert?

Eigentlich darf dies nicht, sein, bzw nicht wirklich.
Ich nehme an, es ist der Compiler ohne Optimierungen.
Wenn in ein Hex compiliert, ist sowas denkbar.
Wenn aber als Lib compiliert und dann mit dem Main zusammengelinkt 
duerfte
dies auch im nicht optimierenden Compiler passen, dass diese Variablen 
nicht
alloziert werden.
Hitech macht eine call analyse und optimiert das ganze, auch in der 
freien
Version. Einzige Limitation dabei ist, rekursive Funktionen sind 
verboten.

von Karl H. (kbuchegg)


Lesenswert?

be stucki schrieb:

> Fände ich aber auch merkwürdig, da das Array in meinem Beispiel auf Null
> initialisiert wird und alle PICs den Befehl CLRF (Register löschen)
> besitzen...

Finde ich überhaupt nicht merkwürdig. Denn du wirst es kaum schaffen, 
ein komplettes Array in ein Register zu quetschen.

>> Denn dieses Array muss ja bei jedem Funktionseintritt neu
>> initialisiert werden.
> Habe ich nicht explizit nachgeschaut, weil ich die Funktion gar nicht
> aufgerufen habe und somit nicht im Programmspeicher vorhanden ist.

Holla.
Nur weil du eine Funktoin nicht aufrufst, bedeutet das noch lange nicht, 
dass sie nicht auch im Speicher vorhanden ist.
Sie steht im Source Code und damit ist sie erst mal bis zum Beweis des 
Gegenteils im kompletten Programm enthalten.
Alles darüber hinausgehende, wie zb das der Linker sie rauswirft, fällt 
unter das Stichwort 'Optimierung'. Und die kann sein, muss aber nicht.


>> Aber das kannst du ja selber auch machen.
> Auf Null initialisieren schon, aber meine Werte müsste ich mühsam per
> Divisions-Gedöhns berechnen, dann ist die Lookup-Table selbst wieder
> hinfällig.

Dann versteh ich das aber nicht. D.h. du hast sowieso ein konstantes 
Array, oder wie soll ich das verstehen?
Wenn es in dem Sinn konstant ist, dass sich die Werte nach dem 
Compilieren sowieso nicht ändern, dann willst du doch dieses hier
1
void foo()
2
{
3
  uint8_t werte[] = { 1, 2, 3, 4 };
4
5
  ...
6
}
gar nicht.
Wozu jedesmal ein Array beim Funktionsaufruf anlegen und mit immer 
denselben Werten initialisieren, die sich im weiteren Programmlauf 
sowieso nie ändern, wenn es durch
1
void foo()
2
{
3
  static uint8_t werte[] = { 1, 2, 3, 4 };
4
5
  ...
6
}
zur einmaligen Sache wird, die beim Programmstart einmalig erledigt 
wird.

: Bearbeitet durch User
von B. S. (bestucki)


Lesenswert?

Chris schrieb:
> Wenn aber als Lib compiliert und dann mit dem Main zusammengelinkt
> duerfte
> dies auch im nicht optimierenden Compiler passen, dass diese Variablen
> nicht
> alloziert werden.
Der Compiler macht meines Wissens nichts anderes, als zuerst in eine Lib 
zu kompilieren und dann zu linken.

Karl Heinz schrieb:
> Holla.
> Nur weil du eine Funktoin nicht aufrufst, bedeutet das noch lange nicht,
> dass sie nicht auch im Speicher vorhanden ist.
Bis jetzt hat die Sache sich so verhalten, dass unbenutzte Funktionen 
nicht im Programmspeicher landeten. Auch wenn mehrere Funktionen in 
einer Source sind, werden landen nur diese, die auch wirklich benutzt 
werden.

Wenn ich folgendes Programm habe,
1
void main(void){
2
  while(1);
3
}
kann ich beliebig viele Sources hinzufügen, ohne dass der 
Programmspeicher anwächst. Mit Ausnahme der oben erwähnten Arrays.

Karl Heinz schrieb:
> Wozu jedesmal ein Array beim Funktionsaufruf anlegen und mit immer
> denselben Werten initialisieren, die sich im weiteren Programmlauf
> sowieso nie ändern, wenn es durchvoid foo()
> [...]
> zur einmaligen Sache wird, die beim Programmstart einmalig erledigt
> wird.
Da hast du allerdings Recht. Das werde ich noch ändern.

: Bearbeitet durch User
von Carsten (Gast)


Lesenswert?

Hi,


Peter Dannegger schrieb:

Ist zwar nahezu OffTopic, aber deine Aussage ist in praktisch JEDER 
HINSICHT leider völiig Falsch!

> Z.B. viele PIC haben keinen echten Stack (kein PUSH/POP, SP). Sie können
> nur eine begrenzte Zahl von Returnadressen in einer Art FIFO speichern.
> Das macht einem Compiler das Leben sehr schwer.

Das die PICs keinen STACK haben trifft nur auf die wirklich wirklich 
alten Bausteine die DEUTLICH über 20 Jahre sind zu. Ich sage mal alles 
was ab 1994 auf den Markt gekommen ist hat einen echten Hardwarestack! 
(mein erstes PIC Buch mit Drucklegung von 1995 beschreibt den schon!)

Selbst die nun auch Fast 20 Jahre alten Oldtimer wie der PIC16C71 uoder 
immer noch verwendete PIC16C84 (Ist gleich 16F84, nur anfangs mit EEPROM 
Programmspeicher statt Flash...) haben bereits einen 8 Ebenen tiefen 
Hardwarestack. Und die waren 1995 schon verfügbar...

Was aber halt Tatsache ist, das du selbst nichts direkt auf den STACK 
schieben oder wieder von diesem holen kannst. (Push / Pop) Ist rein für 
Sprünge... Zwischenspeichern von Werten geht nur über RAM.

> Deshalb gibt es C-Compiler für PIC auch noch nicht so lange, wie z.B.
> für 8051 oder AVR

Für 8051 müsste ich nachsehen, vermute aber das dieser Teil stimmt. 
Hinsichtlich der AVR liegst du aber gewaltig daneben! Denn C compiler 
für die PIC gibt es schon DEUTLICh länger als es überhaupt AVR gibt!
Laut dem MPLAB USERS GUIDE vor mir das laut Angabe 1994 gedruckt wurde 
war damals bereits der Bytecraft MPC Compiler für PIC verfügbar. (AVR 
wurden wohl 1996 erstmals vorgestellt). Es war halt nur Payware...
Wie sinnvoll ein C compiler für die "alten" PIC war sei dabei mal 
dahingestellt. Ich habe es 98 getestet und für noch recht Sinnfrei 
Empfunden wenn man bei den "günstigen" Varianten bleiben wollte und 
nicht
 >>20DM für die damalige Spitzenklasse an PIC µC (auch nur 8Bit) 
ausgeben wollte...

Die AVR sind damals einfach so schnell in Bastlerkreisen populär 
geworden weil die als "Neuentwicklung" keine alten Zöpfe mehr mittragen 
mussten (Die Grundlagen der PIC16 sind nun mittlerweile fast 30 Jahre 
alt) und die Anfangsstrategie darin bestand wenige Typen - diese aber 
mit im vergleich viel Leistung - günstig in den Markt zu pushen. 
1999/2000 war hinsichtlich RAM und Befehlstakt das Preis 
Leistungsverhältnis der AVR weit besser als das der PICs. Für Bastler 
war das Maßgebend. Das dann schnell ein freier C Compiler verfügbar war 
der dank der Leistung auch Sinnvoll bei den günstigen µC eingesetzt 
werden konnte tat sein übriges.
Das ist schon lange alles wieder anders, aber Vorurteile halten sich 
bekanntlich ewig...

> und sie haben oft Einschränkungen.
Diese wären???

Meh-meh-mek schrieb:
> Ein vernuenftiger Compiler schaut eh, dass er soviele Variablen wie
> moeglich in den Registern hat. Der Hardware Stck des PIC ist fuer
> Interrupts.
Nicht nur für Interrups sondern für ALLE Sprünge. Aber einen direkten 
Zugriff gibt es wie oben geschrieben halt nicht...

Gruß
Carsten

von Carsten (Gast)


Lesenswert?

be stucki schrieb:
> Bis jetzt hat die Sache sich so verhalten, dass unbenutzte Funktionen
> nicht im Programmspeicher landeten. Auch wenn mehrere Funktionen in
> einer Source sind, werden landen nur diese, die auch wirklich benutzt
> werden.
>
> Wenn ich folgendes Programm habe,void main(void){
>   while(1);
> }kann ich beliebig viele Sources hinzufügen, ohne dass der
> Programmspeicher anwächst. Mit Ausnahme der oben erwähnten Arrays.

Nun Ja:
Es ist ja nun einmal so das Funktion != Array (bzw. Variable) ist.

Es ist ja für den Compiler bzw. Linker ein leichtes Festzustellen das 
bestimmte Funktionen niemals aufgerufen werden können. Dann lässt er die 
Weg.

Bei Variablen ist es nicht ganz so leicht. Sicher gibt es auch 
Möglichkeiten zumindest bei einigen Programmen auszuschließen das 
bestimmte Speicherbereiche gelesen werden. Aber das ist deutlich 
Aufwendiger und es wird beich realen Programmen sehr häufig nur ein 
"vielleicht" bei rumkommen.
Bei Funktionsaufrufen ist das ja ganz klar an den Sprungadressen 
festzumachen. Entweder irgendwo findet sich später ein Sprung an den 
Anfang der Funktion oder es findet sich keiner...
Bei Variablen (ram allgemein) ist es hingegen nicht mit einem überprüfen 
der direken Adressaufrufe getan denn es gibt ja noch die Möglichkeit der 
Indirekten Adressierung. Es muss also zusätzlich sichergestellt werden 
das unter keinen Umständen der Speicherbereich jemals durch das Programm 
erreicht werden könnte... Bei großen Projekten mit vielen Quelldateien 
die üblicherweise jede für sich compiliert werden nicht ganz so trivial.

Ich will aber trotzdem nicht ausschließen das es in einer höheren 
Optimierungsstufe derartige Überprüfungen gibt. Halte es aber dennoch 
für eher Unwichtig weil es praktisch keinen vernünftigen Grund gibt ein 
nichtbenutztes Array anzulegen.

Gruß
Carsten

von B. S. (bestucki)


Angehängte Dateien:

Lesenswert?

So, ich bin jetzt dazu gekommen die Sachen mal alle hochzuladen. Ich 
habe nur die drei Datein (main.c, foo.c und foo.h) zum Projekt 
hinzugefügt und anschliessend kompiliert.

Resultat:
Programmspeicher: 18 Words
RAM: 12 Bytes


Im Disassembly sieht man schön, wie er die 10 Bytes löscht. In test.lst 
ist ersichtlich, dass diese 10 Bytes tatsächlich im RAM belegt werden. 
Die Funktion foo ist laut funclist.txt nicht im Programmspeicher 
abgelegt.


Setze ich in der Funktion foo ein static vor die Variable, sieht die 
Sache gleich aus. Dann ist es wohl so, dass ich nicht verwendete Sources 
mit static Variablen und Arrays die initialisiert werden nicht zum 
Projekt hinzufügen darf, wenn ich sie nicht verwenden will...

von 8051 (Gast)


Lesenswert?

Kann jemand das Thema in den Pic Bereich verschieben?

Danke!

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.