Hallo,
ich dachte das Handling der Harvard Architektur mit Atmel und gcc jetzt
halbwegs begriffen zu haben, aber hier passiert wieder was komisches:
In einem externen Modul liegt eine Handvoll Bytes, welche so definiert
werden:
prog_uchar war mir bislang nicht so geläufig, aber das wird so von
leubi's Grafikkonverter generiert und ist laut libc-doc nur ein typedef
für "unsigned char PROGMEM".
Wo das letztlich im Speicher hinkommt, kann eigentlich erst der Linker
entscheiden, richtig? Vermutlich deshalb seht im object File des Moduls
auch nur:
1
00000000 R data_plainofen_png
In main.sym ist dann aber zu lesen, dass die Daten nach dem linken
nach 0x11bc kommen:
1
000011bc T data_plainofen_png
Damit soll eine Funktion der Grafiklibrary von Jan Michel aufgerufen
werden. Nämlich:
Nun hätte ich erwartet, dass data_plainofen_png einen 16-bit Pointer auf
eine Flash-Rom Adresse darstellt. Was der Compiler aber daraus gemacht
hat ist folgendes:
1
lcd_draw_image_xy_P( data_plainofen_png , 0, 0, 8, 33, NORMAL );
2
13f6: 80 91 bc 11 lds r24, 0x11BC
3
13fa: 90 91 bd 11 lds r25, 0x11BD
4
13fe: 60 e0 ldi r22, 0x00 ; 0
Was natürlich nicht funktioniert.
Ich habe schon herausgefunden, dass es irgendwie an der Deklaration in
main.c liegt. Was ja auch logisch ist, da der Compiler ja vor dem Linken
die Module völlig unabhängig betrachten muss und nur die Deklaration aus
dem aktuell bearbeiteten File verwenden kann.
Mit
1
externuint8_tdata_plainofen_png[]PROGMEM;
funktioniert es nämlich:
1
lcd_draw_image_xy_P( data_plainofen_png , 0, 0, 8, 33, NORMAL );
2
13f6: 8c eb ldi r24, 0xBC ; 188
3
13f8: 91 e1 ldi r25, 0x11 ; 17
Aber warum ist
1
externPGM_Pdata_plainofen_png;
nicht gleichwertig?
Und warum funktioniert auch
1
externuint8_tdata_plainofen_png[];
????
Ich steh' wohl mal wieder auf der Leitung?
viele Grüße,
Klaus
Johann L. schrieb:> Am besten verzichtest du auf das ganze prog_char, prog_void etc. was die> avr-libc so anbietet und verwendest __attribute__((progmem)) aka. PROMEM> wie es im Compiler dokumentiert ist -- nämlich nur für Variablen im> static Storage.
Seh ich auch so.
Je mehr man sich 'clevere' typedefs zurecht legt, desto blöder wird die
ganze Sache.
zb hat wohl jeder in seiner C-Karriere gedacht, dass es eine extrem gute
Idee ist, sich bei einem typedef auf eine Stuct, auch gleich einen
typedef für einen Pointer auf so eine struct zu machen
1
typedefstruct
2
{
3
intMember1;
4
charMember2;
5
}myStruct,*myStruct_p;
denn dann kann man so wunderbar schreiben
1
intmain()
2
{
3
myStructa;// das ist ein Objekt
4
myStruct_pb;// das ist nur ein Pointer auf so ein Objekt
5
6
...
7
}
Und noch jeder den ich kenne (inklusive mir), hat das wieder aufgegeben.
Irgendwann kommt nämlich Chaos raus und man weiß nicht mehr welcher
Datentyp jetzt ein Pointer ist und welcher nicht. typedef ist gut. Aber
wenn man es übertreibt, dann macht man die Sachen nur schlimmer. Es gibt
Dinge, die will man im Code explizit aus 3 Meter Entfernung sehen.
PROGMEM ist zb so etwas, genauso wie man den Pointer * sehen will.
Karl Heinz Buchegger schrieb:> Johann L. schrieb:>>> Am besten verzichtest du auf das ganze prog_char, prog_void etc. was die>> avr-libc so anbietet und verwendest __attribute__((progmem)) aka. PROMEM>> wie es im Compiler dokumentiert ist -- nämlich nur für Variablen im>> static Storage.>> Seh ich auch so.
Leider ist das Zeug durch die avr-libc so weit gestreut, daß es wohl
kaum mehr auszurotten ist. Es sei denn durch die avr-libc selbst, indem
sie die Typen deprecated.
In avr-gcc gibt's keinen Mechanismus, die das Funktionieren dieser Typen
sicherstellt, und leider gibt's noch nicht mal ne Warning oder so.
Anstatt an der Stelle rumzufrickeln arbeite ich lieber an neuen
Features, Optimierungen und Bug-Fixes.
An neuen Features stechen mir vor allem Named Address Spaces in der Nase
sowie ein Pragma für progmem :-) Ersteres ist leider so viel Arbeit,
daß ich nicht weiß, ob das für die 4.7 annähernd machbar ist.
ufff, jetzt bin ich erstmal noch mehr verwirrt.
Also eure Einwände habe ich (hoffentlich) verstanden. Zum einen was
Typedefs generell angeht. Und wenn ich das mit den Attributen richtig
sehe, funktioniert es zwar, aber ohne Garantie weil nicht spezifiziert.
Klar also dass, man es besser nicht verwendet.
Allerdings kann ich mir damit das konkrete Verhalten in meinem Fall
trotzdem nicht erklären. Denn die typedef Attribute kommen da ja noch
gar nicht zum tragen. Sie stehen in einem anderen Modul als der Code
welcher den zweifelhaften assembler code erzeugt, kommen also erst beim
linken in "kontakt".
Ich versuche es nochmal anders:
1
externuint8_tdata_plainofen_png[]PROGMEM;
2
3
externPGM_Pdata_plainofen_png;
Ich dachte eigentlich die beiden Deklarationen wären gleichwertig.
Vielleicht nicht was irgendwelche Typprüfungen angeht, aber am schluss
ist doch in beiden Fällen data_plainofen_png ein pointer auf eine
flashrom Adresse, oder?
Klaus W. schrieb:> Und wenn ich das mit den Attributen richtig sehe, funktioniert es zwar,> aber ohne Garantie weil nicht spezifiziert.> Klar also dass, man es besser nicht verwendet.
Jepp.
> Ich versuche es nochmal anders:
1
>externuint8_tdata_plainofen_png[]PROGMEM;
2
>
3
>externPGM_Pdata_plainofen_png;
Das zweite ist ein Zeiger, ersteres nicht. Beim zweiten kannst du zB
einen Wert zuweisen, wes im ersten Falle zu einer Fehlermeldung führt.
Zuckerle schrieb im Beitrag #2276408:
> ...
Nach dem Motto:
F: Ich hab da ne Frage zu meinem VW (geschenkt bekommen).
A: Alles Käse, kauf dir nen Mercedes.
Zuckerle schrieb im Beitrag #2276408:
> Also Demoversion saugen, 30 Tage probieren, dann kaufen, ich habe> fertig...
Ne, lass mal.
Nur weil ich vermutlich C als solches noch nicht ganz im Griff habe,
hilft auch erstmal kein Wundercompiler :-).
Klaus W. schrieb:> extern uint8_t data_plainofen_png[] PROGMEM;>> extern PGM_P data_plainofen_png;>> Ich dachte eigentlich die beiden Deklarationen wären gleichwertig.> Vielleicht nicht was irgendwelche Typprüfungen angeht, aber am schluss> ist doch in beiden Fällen data_plainofen_png ein pointer auf eine> flashrom Adresse, oder?
1
externuint8_tdata_plainofen_png[];
An der Adresse repräsentiert durch das Symbol data_plainofen_png (also
0x11bc) ist ein Array zu finden.
1
lcd_draw_image_xy_P(data_plainofen_png,...
Die Adresse des Arrays (also 0x11bc) wird als Pointer an die Funktion
übergeben.
1
externuint8_t*data_plainofen_png;
An der Adresse repräsentiert durch das Symbol data_plainofen_png (also
0x11bc) ist ein Pointer zu finden.
1
lcd_draw_image_xy_P(data_plainofen_png,...
Dieser Pointer (also die beiden Bytes bei 0x11bc) wird an die Funktion
übergeben.
Klaus W. schrieb:> Und warum funktioniert auch
1
externuint8_tdata_plainofen_png[];
> ????
Weil es so was wie einen Flash-Pointer gar nicht gibt. Ein Pointer ist
ein Pointer. Erst die Verwendung eines Pointers entscheidet darüber, ob
er ins RAM, FLASH oder EEPROM zeigt.
Also ich beginne zu begreifen :-).
Johann hat mich schon auf die richtige Spur gebracht.
Und Stefan hat formuliert, was mir eben beim Zähneputzen gedämmert hat
:-).
Also ich fasse nochmal mit eigenen Worten zusammen.
Wenn man den ganzen extern und Progmem-Kram weglässt geht es wieder um
den Unterschied von
1
uint8_t*foo;
und
1
uint8_tfoo[];
darüber bin ich schon mal gestolpert, wenn ich mich recht erinnere.
Irgendwie ist sich das Objekt foo auch in beiden Fällen etwas ähnlich,
weil man es beide male dereferenzieren kann. Im ersten Fall wird aber
Speicher (Ram) bereitgestellt in dem die Adresse abgelegt wird. Im
zweiten hingegen handelt es sich um eine Konstante, die schon zur
Compilierzeit bekannt ist (oder evtl. auch beim Linken wie bei mir).
Dementsprechend erzeugt der Compiler für den Vorgang des
Dereferenzierens unterschiedlichen Code. Das tut er auch wenn die
Symbole extern deklariert sind. Nur passen die Informationen die dann
beim Linken aus dem externen Modul kommen nicht zusammen.
Aber moment mal, das bedeutet nebenbei eigentlich, dass der Compiler
über Modulgrenzen hinweg keinerlei Typprüfungen durchführen kann? Baut
der Linker das ganze nur noch anhand der Namen zusammen?
Klaus W. schrieb:> Wenn man den ganzen extern und Progmem-Kram weglässt geht es wieder um> den Unterschied von
1
>uint8_t*foo;
> und
1
>uint8_tfoo[];
>> Irgendwie ist sich das Objekt foo auch in beiden Fällen etwas ähnlich,> weil man es beide male dereferenzieren kann. Im ersten Fall wird aber> Speicher (Ram) bereitgestellt in dem die Adresse abgelegt wird. Im> zweiten hingegen handelt es sich um eine Konstante, die schon zur> Compilierzeit bekannt ist (oder evtl. auch beim Linken wie bei mir).
Etwas genauer: im zweiten Fall ist die Konstante nicht zur Compilezeit
bekannt, aber der Compiler weiss, daß es eine Konstante ist und kann
entsprechenden Code erzeugen. Der Code enthält dann einen Platzhalter,
der vom Linker/Locator ausgefüllt wird. Aber die vom Compiler erzeugte
Instruktion sieht so aus als wäre es eine Compilerzeit-Konstante.
Klarer wird es, wenn du nicht ein Disassembly anschaust, sonder den vom
Compiler erzeugten Assemblercode (s-File) mit -save-temps -fverbose-asm
> Dementsprechend erzeugt der Compiler für den Vorgang des> Dereferenzierens unterschiedlichen Code. Das tut er auch wenn die> Symbole extern deklariert sind.
Ja. extern hat auf's Dereferenzieren keinen Einfluss. Es sagt nur, daß
das Objekt nicht angelegt werden muss. Auch hier wieder ein Blick ins
s-File.
> Aber moment mal, das bedeutet nebenbei eigentlich, dass der Compiler> über Modulgrenzen hinweg keinerlei Typprüfungen durchführen kann?
Ja, denn ein anderes Modul bekommt er ja garnicht zu sehen.
Auch ein Compiler ist kein Hellseher. Was GCC mach, wenn man in dem
Falle mehrere Quellen gleichzeitig angibt anstatt tröpfchenweise Datei
für Datei, das musst ausprobieren :-)
> Baut der Linker das ganze nur noch anhand der Namen zusammen?
Ja. Sowas würe ein Fall für Lint et al. Vielleicht hilft -fno-common wie
die Symbole unterschiedliche Größe haben, so daß der Linker dann meckern
kann.
Johann L. schrieb:> Etwas genauer: im zweiten Fall ist die Konstante nicht zur Compilezeit> ....
Ich glaub jetzt hab ich's verstanden.
> Ja. Sowas würe ein Fall für Lint et al. Vielleicht hilft -fno-common wie> die Symbole unterschiedliche Größe haben, so daß der Linker dann meckern> kann.
Hm, also auf die Schnelle ins Manual geguckt macht no-common eigentlich
was anderes, aber das guck ich mir mal noch in Ruhe an.
Vielen Dank und viele Grüße,
Klaus