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
voidfoovoid{
2
uint8_ta;// RAM wird nicht reserviert
3
uint8_tb=0;// RAM wird nicht reserviert
4
5
uint8_tc[10];// RAM wird nicht reserviert
6
uint8_td[10]={0};// RAM wird reserviert
7
8
constuint8_te[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.
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.
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.
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.
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.
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.
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.
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.
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
voidfoo()
2
{
3
uint8_twerte[]={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
voidfoo()
2
{
3
staticuint8_twerte[]={1,2,3,4};
4
5
...
6
}
zur einmaligen Sache wird, die beim Programmstart einmalig erledigt
wird.
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
voidmain(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.
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
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
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...