Hallo,
Ich habe schon länger eine Unklarheit zu erdulden wenn ich mit
verschachtelten Zeiger-Konstrukten hantiere, welche auf
unterschiedlichen Ebenen type-qualifier und außerdem das PROGMEM
Attribut verwenden.
I)
z.b. wenn es um Arrays im Flash geht.
Beispiel, wie in doku avr-lib c erläutert:
Es soll ein Array aus Strings im Flash landen. Dies soll man
folgendermaßen machen:
1
constcharcmd1P[]PROGMEM="v5 ";
2
constcharcmd2P[]PROGMEM="vs ";
3
4
constchar(const*tab_P[ARRAYSIZE])PROGMEM={
5
cmd1P,
6
cmd2P,
7
0
8
};
in avr-libc wird PGM_P verwendet, ich habe versucht das Äquivalent in
const char ... PROGMEM umzuwandeln.
Obiger Ausdruck soll Folgendes bedeuten, von innen nach außen:
- ein Array tab_P mit ARRAYSIZE Elementen der im Flash landet
(soweit ich verstehe wirkt PROGMEM immer auf das innerste konstrukt,
also den Array,
- der Array hat Zeiger als Elemente,
- die Zeiger sind const,
(hier bin ich mir nicht sicher ob das const innerhalb der Klammer genau
das bewirkt, und ob z.b. (* const tab_P[ARRAYSIZE]) äquivalent ist, also
ein Array mit const Zeiger Elementen (const * array[]) das selbe ist wie
ein const Array mit Zeiger elementen (* const array[]). Die Klammerung
in der Dekl. ist für Lesbarkeit, sie wegzulassen müsste ein äquivalenter
Ausdruck sein, meine ich.)
- die Zeiger zeigen auf Objekte vom typ const char im Flash.
II)
nun versuche ich einen Array mit Zeiger auf Funktionen in den Flash zu
packen, dessen returntype zudem dabei weggecasted werden soll:
1
externuint8_tPWR_V5_Enable(void);
2
externuint8_tPWR_VS_Enable(void);
3
4
void(*constfunc_tab_P[ARRAYSIZE])(void)PROGMEM={
5
6
(void(*)())PWR_V5_Enable,
7
(void(*)())PWR_VS_Enable,
8
0
9
10
};
Hier ist auch die Frage ob c den cast so versteht wie ich meine:
(void ()) wäre ein cast auf eine void func?
(void (*)()) wäre ein cast auf einen Zeiger auf eine void func?
Die Bedeutung des obigen Ausdrucks soll nach meinem Verständnis also
sein:
void (* const func_tab_P[ARRAYSIZE])(void) PROGMEM
=>
- Array in Progmem, der const ist,
- welcher Elemente von Typ Zeiger hat,
- welche selbst auf void (void) Funktionen zeigen (also auf Flash
Adressen)
Anders als beim String Array sind die Objekte, dessen Adressen die
Array-Elemente sind, selbst zuvor nicht mit Progmem einzeln deklariert,
wie ich meine ist das unnötig, da die Objekte Funktionsadressen sind,
also Flashadressen (somit den Status einen const ... PROGMEM Objektes
haben). Daher sollte der Compiler also NICHT eine Ramvariable anlegen,
dort die Funktionsadresse reinpacken, und die Elemente des Flasharrays
auf diese Ramvariable zeigen lassen, wie es der Fall wäre, wenn man beim
ersten Beispiel die Strings nicht einzeln per PROGMEM deklariert?
Das alles compiliert ohne Warnings bezüglich inkompatiblen
typezuweisungen.
Hast Du mal eine Abschätzung gemacht, wie groß Deine Flash-Daten werden?
Ich würde Progmem nur dann verwenden, wenn dadurch wirklich eine
kritische Menge RAM eingespart wird.
Wenn z.B. Dein AVR 4kB RAM hat, dann tun 2kB konstante Daten darin
überhaupt nicht weh.
Progmem schaltet auch die Fehlermeldungen ab. D.h. Du kannst die
üblichen Zugriffsoperatoren * oder [] verwenden) ohne es zu merken. Das
Programm macht dann natürlich völligen Unsinn.
Peter
Moritz E. schrieb:> I)> z.b. wenn es um Arrays im Flash geht.>> Beispiel, wie in doku avr-lib c erläutert:>> Es soll ein Array aus Strings im Flash landen. Dies soll man> folgendermaßen machen:>>
1
>constcharcmd1P[]PROGMEM="v5 ";
2
>constcharcmd2P[]PROGMEM="vs ";
3
>
4
>constchar(const*tab_P[ARRAYSIZE])PROGMEM={
5
>cmd1P,
6
>cmd2P,
7
>0
8
>};
9
>
>> in avr-libc wird PGM_P verwendet, ich habe versucht das Äquivalent in> const char ... PROGMEM umzuwandeln.>> Obiger Ausdruck soll Folgendes bedeuten, von innen nach außen:>> - ein Array tab_P mit ARRAYSIZE Elementen der im Flash landet
ok
> - der Array hat Zeiger als Elemente,
Ohne die Klammern: ja.
Mit den Klammern: nein.
> - die Zeiger sind const,
mit den Klammern: ja
ohne die Klammern: nein
> Die Klammerung in der Dekl. ist für Lesbarkeit, sie wegzulassen> müsste ein äquivalenter Ausdruck sein, meine ich.)
Diese Annahme stimmt nicht. Die Klammern verändern den ganzen Ausdruck.
Aus einem Array von Pointern wird dann ganz schnell ein Pointer auf ein
Array.
> II)> nun versuche ich einen Array mit Zeiger auf Funktionen in den Flash zu> packen, dessen returntype zudem dabei weggecasted werden soll:
Ein Tip:
Wenn du die Übersicht verlierst, dann benutze typedef um dir Datentypen
zu schaffen und für Klarheit zu Sorgen.
Es spricht nichts dagegen, sich die einzelnen Datentypen erst mal
mittels typedef zurecht zu legen und dann sukzessive die immer
komplexeren Datentypen daraus (gegebenenfalls wieder mit einem typedef)
aufzubauen.
Der Programmierer der nach dir kommt und das alles wieder entwirren
muss, wird es dir danken.
Du willst Funktionspointer benutzen, die auf solche Funktionen
1
externuint8_tPWR_V5_Enable(void);
2
externuint8_tPWR_VS_Enable(void);
zeigen können.
Also machst du dir dafür erst mal einen Datentyp
1
typedefuint8_t(*FnctPtr)(void);
Damit hast du einen Datentyp FnctPtr (du wirst natürlich einen
sinnvolleren Namen nehmen), der ein Pointer auf Funktionen von genau
diesem Typ ist.
Und dann baust du ein Array aus derartigen Funktionspointern
1
FnctPtrfunc_tab_P[ARRAYSIZE]=
2
{
3
PWR_V5_Enable,
4
PWR_VS_Enable
5
};
und du willst dann dieses Array auch noch ins Flash verschieben
1
FnctPtrfunc_tab_P[ARRAYSIZE]PROGMEM=
2
{
3
PWR_V5_Enable,
4
PWR_VS_Enable
5
};
Der typedef macht dir die Sache jetzt leicht, weil du nicht lange mit
Klammern und Sternen hantieren musst. Du hast einen Datentyp für einen
Funktionspointer und mit dem arbeitest du weiter.
Und nochwas: Den Compiler bei Funktionssignaturen anzulügen (sprich mit
Casts zu arbeiten) ist meistens eine ganz, ganz schlechte Idee. Wenn die
Funktion etwas retourniert, dann muss der Compiler das auch wissen. Du
kannst den retournierten Wert ignorieren und nichts damit machen, das
ist ok. Aber dem Compiler eine Funktion, die einen uint8_t returniert
als eine void unterzujubeln, kann je nach Compiler mächtig ins Auge
gehen. Du bist nicht der erste, der für unbedachte Casts mit Abstürzen
bestraft wird! Casts sind Waffen! Man setzt sie nie leichtfertig ein.
Karl Heinz Buchegger schrieb:> Wenn du die Übersicht verlierst, dann benutze typedef um dir Datentypen> zu schaffen
Einziger Nachteil: Attribute kann man nicht (sinnvoll) in einem
typedef unterbringen. Hatten wir ja in der avr-libc versucht, das
ist gründlich daneben gegangen. ;-)
Mit den named address spaces künftig wird das besser, denn die werden
auch über ein typedef mitgereicht (so ich das verstanden habe).
Ja, named address spaces werden durch Qualifier abgebildet, nicht durch
ein Attribut.
PROGMEM aka. __attribute__((progmem)) dient dazu, die Ablage von
Objekten zu beeinflussen, mehr nicht. In einem typedef wäre es — selbst
wenn es dort unterstützt würde — ziemlich witzlos.
Für Zugriffe auf mittels PORGMEM lokatierte Daten muss man eh inline
Assembler verwenden, da führt kein Weg dran vorbei.
Danke für die ausführliche Antwort!
Karl Heinz Buchegger schrieb:>>>> Obiger Ausdruck soll Folgendes bedeuten, von innen nach außen:>>>> - ein Array tab_P mit ARRAYSIZE Elementen der im Flash landet>> ok>
zu:
1
constchar(const*tab_P[ARRAYSIZE])PROGMEM={
wenn es mit Klammern nun ein Zeiger auf ein Array ist (s.u.), sollte das
PROGMEM sich auf den Zeiger beziehen, und nur dieser im Flash landen,
oder?
>> - der Array hat Zeiger als Elemente,>> Ohne die Klammern: ja.> Mit den Klammern: nein.
Was wäre es denn mit den Klammern? Ein Pointer auf ein Array wäre ja
1
constchar(*tab_P)[ARRAYSIZE]PROGMEM={
>> - die Zeiger sind const,>> mit den Klammern: ja> ohne die Klammern: nein
Ich habe nochmal nachgeguckt, und leider ist const char <=> char const,
und nicht so, dass ein Type Qualifier auf das Konstrukt rechts wirkt,
was für mich mehr Sinn gemacht hätte bezüglich Konsistenz der Sprache.
Demnach muss es für ein Array mit const Zeigern auf const char heißen:
1
constchar*consttab_P[ARRAYSIZE]PROGMEM={
Wobei meiner Annahme im ersten Post nach, ein PROGMEM ein const
implizieren würde, und in diesem Fall unnötig sein sollte, die
Zeigerelemente const zu machen, da ein Versuch ein PROGMEM Zeigerelement
zu schreiben ein Fehler produzieren sollte.
>> Die Klammerung in der Dekl. ist für Lesbarkeit, sie wegzulassen>> müsste ein äquivalenter Ausdruck sein, meine ich.)>> Diese Annahme stimmt nicht. Die Klammern verändern den ganzen Ausdruck.> Aus einem Array von Pointern wird dann ganz schnell ein Pointer auf ein> Array.
siehe oben, in dem konkreten Ausdruck, welchen Unterschied würde die
Klammer bedeuten?
> Ein Tip:> Wenn du die Übersicht verlierst, dann benutze typedef um dir Datentypen> zu schaffen und für Klarheit zu Sorgen.>> Es spricht nichts dagegen, sich die einzelnen Datentypen erst mal> mittels typedef zurecht zu legen und dann sukzessive die immer> komplexeren Datentypen daraus (gegebenenfalls wieder mit einem typedef)> aufzubauen.>> Der Programmierer der nach dir kommt und das alles wieder entwirren> muss, wird es dir danken.>> Du willst Funktionspointer benutzen, die auf solche Funktionen>
1
>externuint8_tPWR_V5_Enable(void);
2
>externuint8_tPWR_VS_Enable(void);
3
>
> zeigen können.> Also machst du dir dafür erst mal einen Datentyp>
1
>typedefuint8_t(*FnctPtr)(void);
2
>
> Damit hast du einen Datentyp FnctPtr (du wirst natürlich einen> sinnvolleren Namen nehmen), der ein Pointer auf Funktionen von genau> diesem Typ ist.>
Ja das ist eine gute Idee, ich bin früher schonmal mit typedefs und
PROGMEM gegen die Wand gefahren, was mich wohl so konditioniert hat,
erstmal alle Deklarationen und Zuweisungen per Hand zu verwenden.
>> Und nochwas: Den Compiler bei Funktionssignaturen anzulügen (sprich mit> Casts zu arbeiten) ist meistens eine ganz, ganz schlechte Idee. Wenn die> Funktion etwas retourniert, dann muss der Compiler das auch wissen.
Das extern gehört erstmal nicht dahin, ohne extern würde ich ja den
Compiler nicht anlügen, bzw. ein ansi-compiler sollte das doch
einwandfrei verarbeiten können (Analog zum Aufruf PWR_V5_Enable())"? Der
Grund für void ist, dass in dem Zeiger-Array Funktionen sowohl mit int
als auch mit void aufnehmen soll.
Du
> kannst den retournierten Wert ignorieren und nichts damit machen, das> ist ok. Aber dem Compiler eine Funktion, die einen uint8_t returniert> als eine void unterzujubeln, kann je nach Compiler mächtig ins Auge> gehen. Du bist nicht der erste, der für unbedachte Casts mit Abstürzen> bestraft wird! Casts sind Waffen! Man setzt sie nie leichtfertig ein.
Moritz E. schrieb:>> Und nochwas: Den Compiler bei Funktionssignaturen anzulügen (sprich mit>> Casts zu arbeiten) ist meistens eine ganz, ganz schlechte Idee. Wenn die>> Funktion etwas retourniert, dann muss der Compiler das auch wissen.>> Das extern gehört erstmal nicht dahin, ohne extern würde ich ja den> Compiler nicht anlügen, bzw. ein ansi-compiler sollte das doch> einwandfrei verarbeiten können (Analog zum Aufruf PWR_V5_Enable())"?
es geht nicht um das extern.
Es geht darum, dass deine Funktion einen uint8_t liefert.
> Der> Grund für void ist, dass in dem Zeiger-Array Funktionen sowohl mit int> als auch mit void aufnehmen soll.
Und wie soll der Compiler dann beim tatsächlichen Aufruf wissen, ob die
aufgerufene Funktion etwas liefern wird (und wenn ja: was) oder nicht?
Karl Heinz Buchegger schrieb:> es geht nicht um das extern.> Es geht darum, dass deine Funktion einen uint8_t liefert.>>> Der>> Grund für void ist, dass in dem Zeiger-Array Funktionen sowohl mit int>> als auch mit void aufnehmen soll.>> Und wie soll der Compiler dann beim tatsächlichen Aufruf wissen, ob die> aufgerufene Funktion etwas liefern wird (und wenn ja: was) oder nicht?
Über die Zeiger werde ich die Funktionen nur ohne Rückgabewert
verwenden, die Rückgabewerte sind nur exit-status codes. Anders fiele
mir auch kein Weg ein, Funktionenzeiger mit unterschiedlichen Rückgabe
Types in einen Array zu stecken.
Ich würde annehmen, das die Funktionswerte die in r24 liegen einfach
liegen gelassen werden, die Annahme ist dann, das der Compiler bei einem
Funktionsaufruf im Allgemeinen die für returnwerte vorgesehen Register
r18-r25 nicht als den Aufruf überdauernd vorraussetzt, da in den
Register Usage Guidelines diese unter "Caller-saved" stehen, also der
Caller verantwortlich ist, diese regs vorher zu sichern und nach aufruf
wieder herzustellen, soweit ich das Verstanden habe.
In diesem Fall sollte ein cast auf void doch Bombensicher sein, (anders
als natürlich casts auf was anderes als void)?
Moritz E. schrieb:> Über die Zeiger werde ich die Funktionen nur ohne Rückgabewert> verwenden
Das interessiert die Funktion aber nicht.
Die liefert!
Was du im Prinzip machst
1
uint8_tfoo(void)
2
{
3
return8;
4
}
5
6
voidfoo(void);// Prototyp für obige Funktion, der bewusst falsch ist
7
8
intmain()
9
{
10
foo();
11
}
Bei so etwas klopft dir der Compiler (zu Recht) auf die Finger. Die
Tatsache, dass du mit Funktionspointern und umcasten diesen Code so
verscheleierst, dass der Compiler das nicht mehr merkt, ändert daran
nichts, dass das immer noch ein Fehler ist. (Dasselbe könnte man auch
dadurch erreichen, dass man die Funktion in eine andere Compilation Unit
verlagert und dafür sorgt, dass der Compiler den zugehörigen Prototypen
nicht mit der Funktionsdefinition vergleichen kann)
Lüg deinen Compiler nicht an!
> die Rückgabewerte sind nur exit-status codes. Anders fiele> mir auch kein Weg ein, Funktionenzeiger mit unterschiedlichen Rückgabe> Types in einen Array zu stecken.
Entweder
* alle Funktionen sind gleich
* oder du machst dir zusätzlich eine Typkennung rein, damit du vorher
den Zeiger wieder zum richtigen Funktionstyp casten kannst.
Die 2-te Variante ist die fehleranfälligere (wie alles, was darauf
basiert, dass ein Programmierer keinen Fehler machen darf)
> Ich würde annehmen,
Grundregel:
Triff keine Annahmen darüber, wie der Compiler etwas implementiert.
Damit fällst du nämlich irgendwann auf die Schnauze. Und laut Murphy
genau dann, wenn du es am wenigsten gebrauchen kannst. Spiel nach den
Regeln und erfind keine eigenen.
Ich verstehe nicht warum ihr euch mit so einem Müll
überhaupt beschäftigt. Wer nicht einmal eine gescheite
Speicherorganistaion zustande bringt gehört doch einfach
in die Tonne. Ich sag nur PROG_MEM get pgm_byte wusel
wusel würg. Das hat der Keil C51 schon vor 20 Jahren
besser gemacht. Leute verabschiedet eich von dem Dreck, für
wenig Geld gibt es professionelle Tools. Da kann man sich
seinen Problemen widmen und nicht diesem Compiler gewusel.
>voidfoo(void);// Prototyp für obige Funktion, der bewusst falsch
7
>ist
8
>
9
>intmain()
10
>{
11
>foo();
12
>}
13
>
>> Bei so etwas klopft dir der Compiler (zu Recht) auf die Finger. Die> Tatsache, dass du mit Funktionspointern und umcasten diesen Code so> verscheleierst, dass der Compiler das nicht mehr merkt, ändert daran> nichts, dass das immer noch ein Fehler ist.
Ich hätte es eher damit assoziiert:
1
uint8_tfoo(void)
2
{
3
return8
4
}
5
6
intmain()
7
{
8
foo();
9
}
was ja auch völlig in Ordnung ist.
Zu unterscheiden ist natürlich ob der von mir verwendete cast
"definiert" ist, also nach ansi-c der compiler dies konsistent und
definiert umsetzt (ohne Architekturspezifische/Implementationsabhängige
Seiteneffekte). Bei anderen casts ist das hochgeradig
Implementationsabhängig bzw. nicht von ansi-c definiert, weshalb ich
sonst auch casts nicht viel abgewinnen kann, als "dirty hack" um den
compiler zu belügen wie du sagst. Ist es aber "definiert", ist es ja
keine Lüge ;).
Ob ein Compiler so ein verhalten, wenn denn "definiert", korrekt
umsetzt, ist dann eine andere Frage, auch das es nicht der "beste" Stil
ist, insofern kann ich deinem Standpunkt auch folgen, dass sowas nicht
als allgemeiner Still gepflegt werden sollte und nur mit Obacht
verwendet werden sollte.
Eine Alternative wäre natürlich eine void Wrapper func, die das selbe
liefert, nur ohne cast:
1
uint8_tintfoo(void)
2
{
3
return8;
4
}
5
6
voidvoidintfoo(void)
7
{
8
intfoo();
9
}
Ich denke das werde dann auch besser so handhaben.
>> was ja auch völlig in Ordnung ist.
Das ist in Ordnung. du musst den Returnwert nicht verwenden.
Aber: Das ist nicht das, was du mit dem Funktionspointer machst.
Moritz E. schrieb:> Wobei meiner Annahme im ersten Post nach, ein PROGMEM ein const> implizieren würde, und in diesem Fall unnötig sein sollte, die> Zeigerelemente const zu machen, da ein Versuch ein PROGMEM Zeigerelement> zu schreiben ein Fehler produzieren sollte.
PROGMEM impliziert nicht const, man kann PROGMEM auch ohne const
verwenden. In neuen Compilerversionen ergibt das aber einen Fehler:
int a __attribute__((progmem)) = 1;
>> error: variable 'a' must be const in order to be put into>> read-only section by means of '__attribute__((progmem))'
Das const zu PROGMEM fällt also nicht vom Himmel, man muss es
hinschreiben, siehe http://gcc.gnu.org/PR44643