Forum: Compiler & IDEs PGM_P Verwirrung


von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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:
1
#include <avr/pgmspace.h>
2
prog_uchar data_plainofen_png[] = {
3
        0x00, 0x00, 0x00, 0xE0, 0xB0, 0x98, 0x8C, 0x84, 0x86, 0x82,
4
        0x82, 0x82, 0x82, 0x83, 0x81, 0x81, 0x81, 0x81, 0x81, 0x83,
5
...
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:
1
void lcd_draw_image_xy_P(PGM_P progmem_image,
2
                         uint8_t x,
3
                         uint8_t y,
4
                         uint8_t pages,
5
                         uint8_t columns,
6
                         uint8_t style)
Und der Aufruf sieht bei mir so aus:
1
lcd_draw_image_xy_P( data_plainofen_png , 0, 0, 8, 33, NORMAL );

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
extern uint8_t data_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
extern PGM_P data_plainofen_png;
nicht gleichwertig?

Und warum funktioniert auch
1
extern uint8_t data_plainofen_png[];
????

Ich steh' wohl mal wieder auf der Leitung?


viele Grüße,
Klaus

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

__attribute__((progmem)) in typedef wird nicht unterstützt in avr-gcc, 
es ist ein nicht-dokumentiertes Feature.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38342#c9
http://savannah.nongnu.org/bugs/?33716

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.

http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html#Variable-Attributes

von Karl H. (kbuchegg)


Lesenswert?

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
typedef struct
2
{
3
  int Member1;
4
  char Member2;
5
} myStruct, *myStruct_p;

denn dann kann man so wunderbar schreiben
1
int main()
2
{
3
  myStruct   a;      // das ist ein Objekt
4
  myStruct_p b;      // 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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
extern uint8_t data_plainofen_png[] PROGMEM;
2
3
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?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
> extern uint8_t data_plainofen_png[] PROGMEM;
2
> 
3
> extern PGM_P data_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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

von Stefan E. (sternst)


Lesenswert?

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
extern uint8_t data_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
extern uint8_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
extern uint8_t data_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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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_t foo[];

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?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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_t foo[];
>
> 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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

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.